mirror of
https://github.com/Luzifer/twitch-bot.git
synced 2024-11-08 16:20:02 +00:00
Add automated messages (#2)
This commit is contained in:
parent
396a6452d9
commit
95be0de55b
6 changed files with 194 additions and 6 deletions
132
automessage.go
Normal file
132
automessage.go
Normal 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
|
||||||
|
}
|
38
config.go
38
config.go
|
@ -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
1
go.mod
|
@ -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
2
go.sum
|
@ -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
9
irc.go
|
@ -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
18
main.go
|
@ -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()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue