2021-08-19 13:33:56 +00:00
|
|
|
package plugins
|
2021-03-27 17:25:23 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/Luzifer/go_helpers/v2/str"
|
2021-08-19 13:33:56 +00:00
|
|
|
"github.com/Luzifer/twitch-bot/twitch"
|
2021-03-27 17:25:23 +00:00
|
|
|
"github.com/go-irc/irc"
|
2021-06-13 13:45:19 +00:00
|
|
|
"github.com/mitchellh/hashstructure/v2"
|
2021-06-11 11:52:42 +00:00
|
|
|
"github.com/pkg/errors"
|
2021-03-27 17:25:23 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
2021-06-11 11:52:42 +00:00
|
|
|
type Rule struct {
|
2021-06-13 13:45:19 +00:00
|
|
|
UUID string `hash:"-" yaml:"uuid"`
|
|
|
|
|
2021-06-11 11:52:42 +00:00
|
|
|
Actions []*RuleAction `yaml:"actions"`
|
2021-03-27 17:25:23 +00:00
|
|
|
|
2021-05-24 23:01:46 +00:00
|
|
|
Cooldown *time.Duration `yaml:"cooldown"`
|
2021-06-07 20:20:19 +00:00
|
|
|
ChannelCooldown *time.Duration `yaml:"channel_cooldown"`
|
|
|
|
UserCooldown *time.Duration `yaml:"user_cooldown"`
|
2021-05-24 23:01:46 +00:00
|
|
|
SkipCooldownFor []string `yaml:"skip_cooldown_for"`
|
2021-03-27 17:25:23 +00:00
|
|
|
|
2021-05-24 23:01:46 +00:00
|
|
|
MatchChannels []string `yaml:"match_channels"`
|
|
|
|
MatchEvent *string `yaml:"match_event"`
|
|
|
|
MatchMessage *string `yaml:"match_message"`
|
|
|
|
MatchUsers []string `yaml:"match_users" `
|
2021-03-27 17:25:23 +00:00
|
|
|
|
2021-05-24 23:01:46 +00:00
|
|
|
DisableOnMatchMessages []string `yaml:"disable_on_match_messages"`
|
2021-03-27 17:25:23 +00:00
|
|
|
|
2021-05-24 23:23:23 +00:00
|
|
|
Disable *bool `yaml:"disable"`
|
|
|
|
DisableOnOffline *bool `yaml:"disable_on_offline"`
|
|
|
|
DisableOnPermit *bool `yaml:"disable_on_permit"`
|
|
|
|
DisableOnTemplate *string `yaml:"disable_on_template"`
|
|
|
|
DisableOn []string `yaml:"disable_on"`
|
|
|
|
EnableOn []string `yaml:"enable_on"`
|
2021-03-27 17:25:23 +00:00
|
|
|
|
|
|
|
matchMessage *regexp.Regexp
|
|
|
|
disableOnMatchMessages []*regexp.Regexp
|
2021-08-19 13:33:56 +00:00
|
|
|
|
|
|
|
msgFormatter MsgFormatter
|
|
|
|
timerStore TimerStore
|
|
|
|
twitchClient *twitch.Client
|
2021-03-27 17:25:23 +00:00
|
|
|
}
|
|
|
|
|
2021-06-11 11:52:42 +00:00
|
|
|
func (r Rule) MatcherID() string {
|
2021-06-13 13:45:19 +00:00
|
|
|
if r.UUID != "" {
|
|
|
|
return r.UUID
|
2021-03-27 17:25:23 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 13:45:19 +00:00
|
|
|
h, err := hashstructure.Hash(r, hashstructure.FormatV2, nil)
|
|
|
|
if err != nil {
|
|
|
|
panic(errors.Wrap(err, "hashing automessage"))
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("hashstructure:%x", h)
|
2021-03-27 17:25:23 +00:00
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
func (r *Rule) Matches(m *irc.Message, event *string, timerStore TimerStore, msgFormatter MsgFormatter, twitchClient *twitch.Client) bool {
|
|
|
|
r.msgFormatter = msgFormatter
|
|
|
|
r.timerStore = timerStore
|
|
|
|
r.twitchClient = twitchClient
|
|
|
|
|
2021-03-27 17:25:23 +00:00
|
|
|
var (
|
2021-08-19 13:33:56 +00:00
|
|
|
badges = twitch.ParseBadgeLevels(m)
|
2021-03-27 17:25:23 +00:00
|
|
|
logger = log.WithFields(log.Fields{
|
|
|
|
"msg": m,
|
|
|
|
"rule": r,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
for _, matcher := range []func(*log.Entry, *irc.Message, *string, twitch.BadgeCollection) bool{
|
2021-05-24 23:23:23 +00:00
|
|
|
r.allowExecuteDisable,
|
2021-04-03 12:11:47 +00:00
|
|
|
r.allowExecuteChannelWhitelist,
|
|
|
|
r.allowExecuteUserWhitelist,
|
|
|
|
r.allowExecuteEventWhitelist,
|
|
|
|
r.allowExecuteMessageMatcherWhitelist,
|
|
|
|
r.allowExecuteMessageMatcherBlacklist,
|
|
|
|
r.allowExecuteBadgeBlacklist,
|
|
|
|
r.allowExecuteBadgeWhitelist,
|
|
|
|
r.allowExecuteDisableOnPermit,
|
2021-06-07 20:20:19 +00:00
|
|
|
r.allowExecuteRuleCooldown,
|
|
|
|
r.allowExecuteChannelCooldown,
|
|
|
|
r.allowExecuteUserCooldown,
|
2021-05-24 23:23:23 +00:00
|
|
|
r.allowExecuteDisableOnTemplate,
|
2021-04-03 12:11:47 +00:00
|
|
|
r.allowExecuteDisableOnOffline,
|
|
|
|
} {
|
|
|
|
if !matcher(logger, m, event, badges) {
|
2021-03-27 17:25:23 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-03 12:11:47 +00:00
|
|
|
// Nothing objected: Matches!
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
func (r *Rule) GetMatchMessage() *regexp.Regexp {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
if r.matchMessage == nil {
|
|
|
|
if r.matchMessage, err = regexp.Compile(*r.MatchMessage); err != nil {
|
|
|
|
log.WithError(err).Error("Unable to compile expression")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return r.matchMessage
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Rule) SetCooldown(timerStore TimerStore, m *irc.Message) {
|
2021-06-07 20:20:19 +00:00
|
|
|
if r.Cooldown != nil {
|
2021-08-19 13:33:56 +00:00
|
|
|
timerStore.AddCooldown(TimerTypeCooldown, "", r.MatcherID(), time.Now().Add(*r.Cooldown))
|
2021-06-07 20:20:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if r.ChannelCooldown != nil && len(m.Params) > 0 {
|
2021-08-19 13:33:56 +00:00
|
|
|
timerStore.AddCooldown(TimerTypeCooldown, m.Params[0], r.MatcherID(), time.Now().Add(*r.ChannelCooldown))
|
2021-06-07 20:20:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if r.UserCooldown != nil {
|
2021-08-19 13:33:56 +00:00
|
|
|
timerStore.AddCooldown(TimerTypeCooldown, m.User, r.MatcherID(), time.Now().Add(*r.UserCooldown))
|
2021-06-07 20:20:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
func (r *Rule) allowExecuteBadgeBlacklist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection) bool {
|
2021-04-03 12:11:47 +00:00
|
|
|
for _, b := range r.DisableOn {
|
|
|
|
if badges.Has(b) {
|
|
|
|
logger.Tracef("Non-Match: Disable-Badge %s", b)
|
2021-03-27 17:25:23 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-03 12:11:47 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
func (r *Rule) allowExecuteBadgeWhitelist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection) bool {
|
2021-04-03 12:11:47 +00:00
|
|
|
if len(r.EnableOn) == 0 {
|
|
|
|
// No match criteria set, does not speak against matching
|
|
|
|
return true
|
2021-03-27 17:25:23 +00:00
|
|
|
}
|
|
|
|
|
2021-04-03 12:11:47 +00:00
|
|
|
for _, b := range r.EnableOn {
|
|
|
|
if badges.Has(b) {
|
|
|
|
return true
|
2021-03-27 17:25:23 +00:00
|
|
|
}
|
2021-04-03 12:11:47 +00:00
|
|
|
}
|
2021-03-27 17:25:23 +00:00
|
|
|
|
2021-04-03 12:11:47 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
func (r *Rule) allowExecuteChannelCooldown(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection) bool {
|
2021-06-07 20:20:19 +00:00
|
|
|
if r.ChannelCooldown == nil || len(m.Params) < 1 {
|
2021-04-03 12:11:47 +00:00
|
|
|
// No match criteria set, does not speak against matching
|
|
|
|
return true
|
2021-03-27 17:25:23 +00:00
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
if !r.timerStore.InCooldown(TimerTypeCooldown, m.Params[0], r.MatcherID()) {
|
2021-04-03 12:11:47 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, b := range r.SkipCooldownFor {
|
2021-03-27 17:25:23 +00:00
|
|
|
if badges.Has(b) {
|
2021-04-03 12:11:47 +00:00
|
|
|
return true
|
2021-03-27 17:25:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-03 12:11:47 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
func (r *Rule) allowExecuteChannelWhitelist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection) bool {
|
2021-06-07 20:20:19 +00:00
|
|
|
if len(r.MatchChannels) == 0 {
|
|
|
|
// No match criteria set, does not speak against matching
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(m.Params) == 0 || (!str.StringInSlice(m.Params[0], r.MatchChannels) && !str.StringInSlice(strings.TrimPrefix(m.Params[0], "#"), r.MatchChannels)) {
|
|
|
|
logger.Trace("Non-Match: Channel")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
func (r *Rule) allowExecuteDisable(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection) bool {
|
2021-05-25 10:08:41 +00:00
|
|
|
if r.Disable == nil {
|
|
|
|
// No match criteria set, does not speak against matching
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
if *r.Disable {
|
|
|
|
logger.Trace("Non-Match: Disable")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
2021-05-24 23:23:23 +00:00
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
func (r *Rule) allowExecuteDisableOnOffline(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection) bool {
|
2021-05-24 23:23:23 +00:00
|
|
|
if r.DisableOnOffline == nil || !*r.DisableOnOffline {
|
2021-04-03 12:11:47 +00:00
|
|
|
// No match criteria set, does not speak against matching
|
|
|
|
return true
|
2021-03-27 17:25:23 +00:00
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
streamLive, err := r.twitchClient.HasLiveStream(strings.TrimLeft(m.Params[0], "#"))
|
2021-04-03 12:11:47 +00:00
|
|
|
if err != nil {
|
|
|
|
logger.WithError(err).Error("Unable to determine live status")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if !streamLive {
|
|
|
|
logger.Trace("Non-Match: Stream offline")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
func (r *Rule) allowExecuteDisableOnPermit(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection) bool {
|
|
|
|
if r.DisableOnPermit != nil && *r.DisableOnPermit && r.timerStore.HasPermit(m.Params[0], m.User) {
|
2021-03-27 17:25:23 +00:00
|
|
|
logger.Trace("Non-Match: Permit")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-04-03 12:11:47 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
func (r *Rule) allowExecuteDisableOnTemplate(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection) bool {
|
2021-05-24 23:23:23 +00:00
|
|
|
if r.DisableOnTemplate == nil {
|
|
|
|
// No match criteria set, does not speak against matching
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
res, err := r.msgFormatter(*r.DisableOnTemplate, m, r, nil)
|
2021-05-24 23:23:23 +00:00
|
|
|
if err != nil {
|
|
|
|
logger.WithError(err).Error("Unable to check DisableOnTemplate field")
|
|
|
|
// Caused an error, forbid execution
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-05-25 10:08:41 +00:00
|
|
|
if res == "true" {
|
|
|
|
logger.Trace("Non-Match: Template")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
2021-05-24 23:23:23 +00:00
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
func (r *Rule) allowExecuteEventWhitelist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection) bool {
|
2021-04-03 12:11:47 +00:00
|
|
|
if r.MatchEvent == nil {
|
|
|
|
// No match criteria set, does not speak against matching
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
if event == nil || *r.MatchEvent != *event {
|
|
|
|
logger.Trace("Non-Match: Event")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
func (r *Rule) allowExecuteMessageMatcherBlacklist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection) bool {
|
2021-04-03 12:11:47 +00:00
|
|
|
if len(r.DisableOnMatchMessages) == 0 {
|
|
|
|
// No match criteria set, does not speak against matching
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the regexps were not pre-compiled, do it now
|
|
|
|
if len(r.disableOnMatchMessages) != len(r.DisableOnMatchMessages) {
|
|
|
|
r.disableOnMatchMessages = nil
|
|
|
|
for _, dm := range r.DisableOnMatchMessages {
|
|
|
|
dmr, err := regexp.Compile(dm)
|
|
|
|
if err != nil {
|
|
|
|
logger.WithError(err).Error("Unable to compile expression")
|
|
|
|
return false
|
2021-03-27 17:25:23 +00:00
|
|
|
}
|
2021-04-03 12:11:47 +00:00
|
|
|
r.disableOnMatchMessages = append(r.disableOnMatchMessages, dmr)
|
2021-03-27 17:25:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-03 12:11:47 +00:00
|
|
|
for _, rex := range r.disableOnMatchMessages {
|
|
|
|
if rex.MatchString(m.Trailing()) {
|
|
|
|
logger.Trace("Non-Match: Disable-On-Message")
|
2021-03-27 17:25:23 +00:00
|
|
|
return false
|
|
|
|
}
|
2021-04-03 12:11:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
func (r *Rule) allowExecuteMessageMatcherWhitelist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection) bool {
|
2021-04-03 12:11:47 +00:00
|
|
|
if r.MatchMessage == nil {
|
|
|
|
// No match criteria set, does not speak against matching
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
// If the regexp was not yet compiled, cache it
|
|
|
|
if r.matchMessage == nil {
|
|
|
|
if r.matchMessage, err = regexp.Compile(*r.MatchMessage); err != nil {
|
|
|
|
logger.WithError(err).Error("Unable to compile expression")
|
2021-03-27 17:25:23 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-03 12:11:47 +00:00
|
|
|
// Check whether the message matches
|
|
|
|
if !r.matchMessage.MatchString(m.Trailing()) {
|
|
|
|
logger.Trace("Non-Match: Message")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
func (r *Rule) allowExecuteRuleCooldown(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection) bool {
|
2021-06-07 20:20:19 +00:00
|
|
|
if r.Cooldown == nil {
|
|
|
|
// No match criteria set, does not speak against matching
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
if !r.timerStore.InCooldown(TimerTypeCooldown, "", r.MatcherID()) {
|
2021-06-07 20:20:19 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, b := range r.SkipCooldownFor {
|
|
|
|
if badges.Has(b) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
func (r *Rule) allowExecuteUserCooldown(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection) bool {
|
2021-06-07 20:20:19 +00:00
|
|
|
if r.UserCooldown == nil {
|
|
|
|
// No match criteria set, does not speak against matching
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
if !r.timerStore.InCooldown(TimerTypeCooldown, m.User, r.MatcherID()) {
|
2021-06-07 20:20:19 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, b := range r.SkipCooldownFor {
|
|
|
|
if badges.Has(b) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
func (r *Rule) allowExecuteUserWhitelist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection) bool {
|
2021-04-03 12:11:47 +00:00
|
|
|
if len(r.MatchUsers) == 0 {
|
|
|
|
// No match criteria set, does not speak against matching
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
if !str.StringInSlice(strings.ToLower(m.User), r.MatchUsers) {
|
|
|
|
logger.Trace("Non-Match: Users")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-03-27 17:25:23 +00:00
|
|
|
return true
|
|
|
|
}
|