2021-03-27 16:59:56 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/sha256"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/go-irc/irc"
|
|
|
|
"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)
|
|
|
|
|
|
|
|
type autoMessage struct {
|
2021-05-24 23:01:46 +00:00
|
|
|
Channel string `yaml:"channel"`
|
|
|
|
Message string `yaml:"message"`
|
|
|
|
UseAction bool `yaml:"use_action"`
|
|
|
|
|
2021-05-25 15:05:27 +00:00
|
|
|
DisableOnTemplate *string `yaml:"disable_on_template"`
|
|
|
|
|
2021-05-24 23:01:46 +00:00
|
|
|
Cron string `yaml:"cron"`
|
|
|
|
MessageInterval int64 `yaml:"message_interval"`
|
|
|
|
OnlyOnLive bool `yaml:"only_on_live"`
|
|
|
|
TimeInterval time.Duration `yaml:"time_interval"`
|
2021-03-27 16:59:56 +00:00
|
|
|
|
|
|
|
disabled bool
|
|
|
|
lastMessageSent time.Time
|
|
|
|
linesSinceLastMessage int64
|
|
|
|
|
|
|
|
lock sync.RWMutex
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *autoMessage) CanSend() bool {
|
2021-06-11 12:02:43 +00:00
|
|
|
a.lock.RLock()
|
|
|
|
defer a.lock.RUnlock()
|
|
|
|
|
2021-03-27 16:59:56 +00:00
|
|
|
if a.disabled || !a.IsValid() {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case a.MessageInterval > a.linesSinceLastMessage:
|
|
|
|
// Not enough chatted lines
|
|
|
|
return false
|
|
|
|
|
|
|
|
case a.TimeInterval > 0 && a.lastMessageSent.Add(a.TimeInterval).After(time.Now()):
|
|
|
|
// Simple timer is not yet expired
|
|
|
|
return false
|
|
|
|
|
|
|
|
case a.Cron != "":
|
|
|
|
sched, _ := cronParser.Parse(a.Cron)
|
2021-06-11 13:32:45 +00:00
|
|
|
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
|
|
|
|
}
|
2021-06-11 13:32:45 +00:00
|
|
|
log.WithFields(log.Fields{
|
|
|
|
"lastMessage": a.lastMessageSent,
|
|
|
|
"nextExecution": nextExecute,
|
|
|
|
"now": time.Now(),
|
|
|
|
}).Debug("Auto-Message was allowed through cron")
|
2021-03-27 16:59:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if a.OnlyOnLive {
|
|
|
|
streamLive, err := twitch.HasLiveStream(strings.TrimLeft(a.Channel, "#"))
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("Unable to determine channel live status")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if !streamLive {
|
2021-03-27 19:08:21 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-25 15:05:27 +00:00
|
|
|
if !a.allowExecuteDisableOnTemplate() {
|
2021-05-25 16:40:06 +00:00
|
|
|
log.Trace("Auto-Message disabled by template")
|
|
|
|
// Reset the timer for this execution not to spam every second
|
|
|
|
a.lastMessageSent = time.Now()
|
2021-05-25 15:05:27 +00:00
|
|
|
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 {
|
|
|
|
sum := sha256.New()
|
|
|
|
|
|
|
|
fmt.Fprintf(sum, "channel:%q", a.Channel)
|
|
|
|
fmt.Fprintf(sum, "message:%q", a.Message)
|
|
|
|
fmt.Fprintf(sum, "action:%v", a.UseAction)
|
|
|
|
|
|
|
|
return fmt.Sprintf("sha256:%x", sum.Sum(nil))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *autoMessage) IsValid() bool {
|
|
|
|
if a.Cron != "" {
|
|
|
|
if _, err := cronParser.Parse(a.Cron); err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if a.MessageInterval == 0 && a.TimeInterval == 0 && a.Cron == "" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *autoMessage) Send(c *irc.Client) error {
|
|
|
|
a.lock.Lock()
|
|
|
|
defer a.lock.Unlock()
|
|
|
|
|
2021-05-06 13:19:24 +00:00
|
|
|
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
|
|
|
|
}
|
2021-05-25 15:05:27 +00:00
|
|
|
|
|
|
|
func (a *autoMessage) allowExecuteDisableOnTemplate() bool {
|
|
|
|
if a.DisableOnTemplate == nil {
|
|
|
|
// 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 {
|
2021-05-25 16:40:06 +00:00
|
|
|
log.WithError(err).Error("Error in auto-message disable template")
|
2021-05-25 15:05:27 +00:00
|
|
|
// Caused an error, forbid execution
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return res != "true"
|
|
|
|
}
|