twitch-bot/automessage.go

177 lines
4.2 KiB
Go
Raw Permalink Normal View History

2021-03-27 16:59:56 +00:00
package main
import (
"fmt"
"strings"
"sync"
"time"
"github.com/Luzifer/go_helpers/v2/str"
2021-03-27 16:59:56 +00:00
"github.com/go-irc/irc"
"github.com/mitchellh/hashstructure/v2"
2021-03-27 16:59:56 +00:00
"github.com/pkg/errors"
"github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus"
)
var cronParser = cron.NewParser(cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
2021-03-27 16:59:56 +00:00
type autoMessage struct {
UUID string `hash:"-" json:"uuid,omitempty" yaml:"uuid,omitempty"`
Channel string `json:"channel,omitempty" yaml:"channel,omitempty"`
Message string `json:"message,omitempty" yaml:"message,omitempty"`
UseAction bool `json:"use_action,omitempty" yaml:"use_action,omitempty"`
DisableOnTemplate *string `json:"disable_on_template,omitempty" yaml:"disable_on_template,omitempty"`
Cron string `json:"cron,omitempty" yaml:"cron,omitempty"`
MessageInterval int64 `json:"message_interval,omitempty" yaml:"message_interval,omitempty"`
OnlyOnLive bool `json:"only_on_live,omitempty" yaml:"only_on_live,omitempty"`
2021-03-27 16:59:56 +00:00
disabled bool
lastMessageSent time.Time
linesSinceLastMessage int64
lock sync.RWMutex
}
func (a *autoMessage) CanSend() bool {
a.lock.RLock()
defer a.lock.RUnlock()
2021-03-27 16:59:56 +00:00
if a.disabled || !a.IsValid() {
return false
}
switch {
case !str.StringInSlice(a.Channel, config.Channels):
// Not an observed channel, auto-message is not valid
return false
2021-03-27 16:59:56 +00:00
case a.MessageInterval > a.linesSinceLastMessage:
// Not enough chatted lines
return false
case a.Cron != "":
sched, _ := cronParser.Parse(a.Cron)
nextExecute := sched.Next(a.lastMessageSent)
if nextExecute.After(time.Now()) {
2021-03-27 16:59:56 +00:00
// Cron timer is not yet expired
return false
}
log.WithFields(log.Fields{
"lastMessage": a.lastMessageSent,
"nextExecution": nextExecute,
"now": time.Now(),
}).Trace("Auto-Message was allowed through cron")
2021-03-27 16:59:56 +00:00
}
if a.OnlyOnLive {
streamLive, err := twitchClient.HasLiveStream(strings.TrimLeft(a.Channel, "#"))
2021-03-27 16:59:56 +00:00
if err != nil {
log.WithError(err).Error("Unable to determine channel live status")
return false
}
if !streamLive {
// Timer is only to be triggered during stream being live,
// reset the timer in order not to spam all messages on stream-start
a.lastMessageSent = time.Now()
2021-03-27 16:59:56 +00:00
return false
}
}
if !a.allowExecuteDisableOnTemplate() {
log.Trace("Auto-Message disabled by template")
// Reset the timer for this execution not to spam every second
a.lastMessageSent = time.Now()
return false
}
2021-03-27 16:59:56 +00:00
return true
}
func (a *autoMessage) CountMessage(channel string) {
if strings.TrimLeft(channel, "#") != strings.TrimLeft(a.Channel, "#") {
return
}
a.lock.Lock()
defer a.lock.Unlock()
a.linesSinceLastMessage++
}
func (a *autoMessage) ID() string {
if a.UUID != "" {
return a.UUID
}
2021-03-27 16:59:56 +00:00
h, err := hashstructure.Hash(a, hashstructure.FormatV2, nil)
if err != nil {
panic(errors.Wrap(err, "hashing automessage"))
}
return fmt.Sprintf("hashstructure:%x", h)
2021-03-27 16:59:56 +00:00
}
func (a *autoMessage) IsValid() bool {
if a.Cron != "" {
if _, err := cronParser.Parse(a.Cron); err != nil {
return false
}
}
if a.MessageInterval == 0 && a.Cron == "" {
2021-03-27 16:59:56 +00:00
return false
}
return true
}
func (a *autoMessage) Send(c *irc.Client) error {
a.lock.Lock()
defer a.lock.Unlock()
msg, err := formatMessage(a.Message, nil, nil, nil)
if err != nil {
return errors.Wrap(err, "preparing message")
}
2021-03-27 16:59:56 +00:00
if a.UseAction {
msg = fmt.Sprintf("\001ACTION %s\001", msg)
}
if err := c.WriteMessage(&irc.Message{
Command: "PRIVMSG",
Params: []string{
fmt.Sprintf("#%s", strings.TrimLeft(a.Channel, "#")),
msg,
},
}); err != nil {
return errors.Wrap(err, "sending auto-message")
}
a.lastMessageSent = time.Now()
a.linesSinceLastMessage = 0
return nil
}
func (a *autoMessage) allowExecuteDisableOnTemplate() bool {
if a.DisableOnTemplate == nil || *a.DisableOnTemplate == "" {
// No match criteria set, does not speak against matching
return true
}
res, err := formatMessage(*a.DisableOnTemplate, nil, nil, map[string]interface{}{
"channel": a.Channel,
})
if err != nil {
log.WithError(err).Error("Error in auto-message disable template")
// Caused an error, forbid execution
return false
}
return res != "true"
}