Add automated messages (#2)

This commit is contained in:
Knut Ahlers 2021-03-27 17:59:56 +01:00 committed by GitHub
parent 396a6452d9
commit 95be0de55b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 194 additions and 6 deletions

132
automessage.go Normal file
View file

@ -0,0 +1,132 @@
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 {
Channel string `yaml:"channel"`
Message string `yaml:"message"`
UseAction bool `yaml:"use_action"`
Cron string `yaml:"cron"`
MessageInterval int64 `yaml:"message_interval"`
OnlyOnLive bool `yaml:"only_on_live"`
TimeInterval time.Duration `yaml:"time_interval"`
disabled bool
lastMessageSent time.Time
linesSinceLastMessage int64
lock sync.RWMutex
}
func (a *autoMessage) CanSend() bool {
if a.disabled || !a.IsValid() {
return false
}
a.lock.RLock()
defer a.lock.RUnlock()
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)
if sched.Next(a.lastMessageSent).After(time.Now()) {
// Cron timer is not yet expired
return false
}
}
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 {
return false
}
}
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()
msg := a.Message
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
}

View file

@ -17,10 +17,11 @@ import (
) )
type configFile struct { type configFile struct {
Channels []string `yaml:"channels"` AutoMessages []*autoMessage `yaml:"auto_messages"`
PermitAllowModerator bool `yaml:"permit_allow_moderator"` Channels []string `yaml:"channels"`
PermitTimeout time.Duration `yaml:"permit_timeout"` PermitAllowModerator bool `yaml:"permit_allow_moderator"`
Rules []*rule `yaml:"rules"` PermitTimeout time.Duration `yaml:"permit_timeout"`
Rules []*rule `yaml:"rules"`
} }
func newConfigFile() configFile { func newConfigFile() configFile {
@ -236,6 +237,35 @@ func loadConfig(filename string) error {
log.Warn("Loaded config with empty ruleset") log.Warn("Loaded config with empty ruleset")
} }
for idx, nam := range tmpConfig.AutoMessages {
// By default assume last message to be sent now
// in order not to spam messages at startup
nam.lastMessageSent = time.Now()
if !nam.IsValid() {
log.WithField("index", idx).Warn("Auto-Message configuration is invalid and therefore disabled")
}
if config == nil {
// Initial config load, do not update timers
continue
}
for _, oam := range config.AutoMessages {
if nam.ID() != oam.ID() {
continue
}
// We disable the old message as executing it would
// mess up the constraints of the new message
oam.lock.Lock()
oam.disabled = true
nam.lastMessageSent = oam.lastMessageSent
nam.linesSinceLastMessage = oam.linesSinceLastMessage
}
}
configLock.Lock() configLock.Lock()
defer configLock.Unlock() defer configLock.Unlock()

1
go.mod
View file

@ -9,6 +9,7 @@ require (
github.com/go-irc/irc v2.1.0+incompatible github.com/go-irc/irc v2.1.0+incompatible
github.com/gologme/log v1.2.0 github.com/gologme/log v1.2.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/robfig/cron/v3 v3.0.1
github.com/sirupsen/logrus v1.7.0 github.com/sirupsen/logrus v1.7.0
gopkg.in/fsnotify.v1 v1.4.7 gopkg.in/fsnotify.v1 v1.4.7
gopkg.in/yaml.v2 v2.2.2 gopkg.in/yaml.v2 v2.2.2

2
go.sum
View file

@ -83,6 +83,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 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 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=

9
irc.go
View file

@ -167,6 +167,15 @@ func (i ircHandler) handleTwitchPrivmsg(m *irc.Message) {
"trailing": m.Trailing(), "trailing": m.Trailing(),
}).Trace("Received privmsg") }).Trace("Received privmsg")
if m.User != i.user {
// Count messages from other users than self
configLock.RLock()
for _, am := range config.AutoMessages {
am.CountMessage(m.Params[0])
}
configLock.RUnlock()
}
if strings.HasPrefix(m.Trailing(), "!permit") { if strings.HasPrefix(m.Trailing(), "!permit") {
i.handlePermit(m) i.handlePermit(m)
return return

18
main.go
View file

@ -70,8 +70,9 @@ func main() {
} }
var ( var (
irc *ircHandler irc *ircHandler
ircDisconnected = make(chan struct{}, 1) ircDisconnected = make(chan struct{}, 1)
autoMessageTicker = time.NewTicker(time.Second)
) )
ircDisconnected <- struct{}{} ircDisconnected <- struct{}{}
@ -110,6 +111,19 @@ func main() {
log.Info("Config file reloaded") log.Info("Config file reloaded")
case <-autoMessageTicker.C:
configLock.RLock()
for _, am := range config.AutoMessages {
if !am.CanSend() {
continue
}
if err := am.Send(irc.c); err != nil {
log.WithError(err).Error("Unable to send automated message")
}
}
configLock.RUnlock()
} }
} }
} }