2021-11-03 13:48:30 +00:00
|
|
|
package twitch
|
|
|
|
|
|
|
|
import (
|
|
|
|
"math/rand"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2022-01-27 15:49:18 +00:00
|
|
|
"go.uber.org/zap"
|
|
|
|
|
2022-03-24 09:16:51 +00:00
|
|
|
irc "github.com/gempir/go-twitch-irc/v3"
|
2021-11-03 13:48:30 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const BotTimersKey = "twitch/bot-modules/timers/config"
|
|
|
|
|
|
|
|
type BotTimersConfig struct {
|
|
|
|
Timers map[string]BotTimer `json:"timers"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type BotTimer struct {
|
|
|
|
Enabled bool `json:"enabled"` // Whether the timer is enabled
|
|
|
|
Name string `json:"name"` // Timer name (must be unique)
|
|
|
|
MinimumChatActivity int `json:"minimum_chat_activity"` // Minimum chat messages in the last 5 minutes
|
|
|
|
MinimumDelay int `json:"minimum_delay"` // In seconds
|
|
|
|
Messages []string `json:"messages"` // Messages to write (randomly chosen)
|
|
|
|
}
|
|
|
|
|
|
|
|
const AverageMessageWindow = 5
|
|
|
|
|
|
|
|
type BotTimerModule struct {
|
|
|
|
Config BotTimersConfig
|
|
|
|
|
|
|
|
lastTrigger map[string]time.Time
|
|
|
|
bot *Bot
|
|
|
|
messages [AverageMessageWindow]int
|
|
|
|
mu sync.Mutex
|
|
|
|
startTime time.Time
|
|
|
|
}
|
|
|
|
|
|
|
|
func SetupTimers(bot *Bot) *BotTimerModule {
|
|
|
|
mod := &BotTimerModule{
|
2021-11-05 21:54:42 +00:00
|
|
|
bot: bot,
|
|
|
|
startTime: time.Now().Round(time.Minute),
|
|
|
|
lastTrigger: make(map[string]time.Time),
|
2021-12-05 14:47:53 +00:00
|
|
|
mu: sync.Mutex{},
|
2021-11-03 13:48:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Load config from database
|
|
|
|
err := bot.api.db.GetJSON(BotTimersKey, &mod.Config)
|
|
|
|
if err != nil {
|
2022-01-27 15:49:18 +00:00
|
|
|
bot.logger.Debug("config load error", zap.Error(err))
|
2021-11-03 13:48:30 +00:00
|
|
|
mod.Config = BotTimersConfig{
|
|
|
|
Timers: make(map[string]BotTimer),
|
|
|
|
}
|
2021-12-06 11:23:04 +00:00
|
|
|
// Save empty config
|
2022-12-02 22:52:45 +00:00
|
|
|
err = bot.api.db.PutJSON(BotTimersKey, mod.Config)
|
|
|
|
if err != nil {
|
|
|
|
bot.logger.Warn("could not save default config for bot timers", zap.Error(err))
|
|
|
|
}
|
2021-11-03 13:48:30 +00:00
|
|
|
}
|
|
|
|
|
2022-11-25 19:57:52 +00:00
|
|
|
err = bot.api.db.SubscribeKey(BotTimersKey, func(value string) {
|
2022-11-25 13:16:20 +00:00
|
|
|
err := json.UnmarshalFromString(value, &mod.Config)
|
|
|
|
if err != nil {
|
|
|
|
bot.logger.Debug("error reloading timer config", zap.Error(err))
|
|
|
|
} else {
|
|
|
|
bot.logger.Info("reloaded timer config")
|
2021-11-24 11:43:55 +00:00
|
|
|
}
|
2022-11-25 19:57:52 +00:00
|
|
|
})
|
2022-11-25 13:16:20 +00:00
|
|
|
if err != nil {
|
|
|
|
bot.logger.Error("could not set-up timer reload subscription", zap.Error(err))
|
|
|
|
}
|
2021-11-24 11:43:55 +00:00
|
|
|
|
2022-01-27 15:49:18 +00:00
|
|
|
bot.logger.Debug("loaded timers", zap.Int("timers", len(mod.Config.Timers)))
|
2021-11-05 21:54:42 +00:00
|
|
|
|
2021-11-03 13:48:30 +00:00
|
|
|
// Start goroutine for clearing message counters and running timers
|
|
|
|
go mod.runTimers()
|
|
|
|
|
|
|
|
return mod
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *BotTimerModule) runTimers() {
|
|
|
|
for {
|
|
|
|
// Wait until next tick (remainder until next minute, as close to 0 seconds as possible)
|
|
|
|
currentTime := time.Now()
|
|
|
|
nextTick := currentTime.Round(time.Minute).Add(time.Minute)
|
|
|
|
timeUntilNextTick := nextTick.Sub(currentTime)
|
|
|
|
time.Sleep(timeUntilNextTick)
|
|
|
|
|
2022-01-22 12:05:24 +00:00
|
|
|
err := m.bot.api.db.PutJSON(ChatActivityKey, m.messages)
|
|
|
|
if err != nil {
|
2022-01-27 15:49:18 +00:00
|
|
|
m.bot.logger.Warn("error saving chat activity", zap.Error(err))
|
2022-01-22 12:05:24 +00:00
|
|
|
}
|
|
|
|
|
2021-11-03 13:48:30 +00:00
|
|
|
// Calculate activity
|
|
|
|
activity := m.currentChatActivity()
|
|
|
|
|
|
|
|
// Reset timer
|
|
|
|
func() {
|
|
|
|
index := time.Now().Minute() % AverageMessageWindow
|
|
|
|
m.mu.Lock()
|
|
|
|
defer m.mu.Unlock()
|
|
|
|
m.messages[index] = 0
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Run timers
|
|
|
|
func() {
|
|
|
|
now := time.Now()
|
|
|
|
m.mu.Lock()
|
|
|
|
defer m.mu.Unlock()
|
|
|
|
for name, timer := range m.Config.Timers {
|
|
|
|
// Must be enabled
|
2021-12-09 12:32:58 +00:00
|
|
|
if !timer.Enabled {
|
2021-11-03 13:48:30 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
// Check if enough time has passed
|
|
|
|
lastTriggeredTime, ok := m.lastTrigger[name]
|
|
|
|
if !ok {
|
2021-11-05 21:54:42 +00:00
|
|
|
// If it's the first time we're checking it, start the cooldown
|
|
|
|
lastTriggeredTime = time.Now()
|
2021-11-24 11:43:55 +00:00
|
|
|
m.lastTrigger[name] = lastTriggeredTime
|
2021-11-03 13:48:30 +00:00
|
|
|
}
|
2021-11-05 18:30:14 +00:00
|
|
|
minDelay := timer.MinimumDelay
|
2021-11-05 21:54:42 +00:00
|
|
|
if minDelay < 60 {
|
|
|
|
minDelay = 60
|
2021-11-05 18:30:14 +00:00
|
|
|
}
|
|
|
|
if now.Sub(lastTriggeredTime) < time.Duration(minDelay)*time.Second {
|
2021-11-03 13:48:30 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
// Make sure chat activity is high enough
|
|
|
|
if activity < timer.MinimumChatActivity {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pick a random message
|
|
|
|
message := timer.Messages[rand.Intn(len(timer.Messages))]
|
|
|
|
|
|
|
|
// Write message to chat
|
|
|
|
m.bot.WriteMessage(message)
|
|
|
|
|
|
|
|
// Update last trigger
|
|
|
|
m.lastTrigger[name] = now
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *BotTimerModule) currentChatActivity() int {
|
|
|
|
total := 0
|
|
|
|
for _, v := range m.messages {
|
|
|
|
total += v
|
|
|
|
}
|
|
|
|
return total
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *BotTimerModule) OnMessage(message irc.PrivateMessage) {
|
|
|
|
index := message.Time.Minute() % AverageMessageWindow
|
|
|
|
m.mu.Lock()
|
|
|
|
defer m.mu.Unlock()
|
|
|
|
m.messages[index] += 1
|
|
|
|
}
|