2024-03-10 16:38:18 +00:00
|
|
|
package timers
|
2021-11-03 13:48:30 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"math/rand"
|
|
|
|
"time"
|
|
|
|
|
2023-11-10 20:36:15 +00:00
|
|
|
"git.sr.ht/~ashkeel/containers/sync"
|
2024-03-10 16:38:18 +00:00
|
|
|
jsoniter "github.com/json-iterator/go"
|
2022-01-27 15:49:18 +00:00
|
|
|
"go.uber.org/zap"
|
|
|
|
|
2023-11-10 20:36:15 +00:00
|
|
|
"git.sr.ht/~ashkeel/strimertul/database"
|
2024-03-10 16:38:18 +00:00
|
|
|
"git.sr.ht/~ashkeel/strimertul/twitch/chat"
|
2021-11-03 13:48:30 +00:00
|
|
|
)
|
|
|
|
|
2024-03-10 16:38:18 +00:00
|
|
|
var json = jsoniter.ConfigFastest
|
2021-11-03 13:48:30 +00:00
|
|
|
|
|
|
|
const AverageMessageWindow = 5
|
|
|
|
|
2024-03-10 16:38:18 +00:00
|
|
|
type Module struct {
|
|
|
|
Config Config
|
2021-11-03 13:48:30 +00:00
|
|
|
|
2022-12-04 13:45:34 +00:00
|
|
|
lastTrigger *sync.Map[string, time.Time]
|
|
|
|
messages *sync.Slice[int]
|
2022-12-03 15:16:59 +00:00
|
|
|
|
2024-03-10 16:38:18 +00:00
|
|
|
logger *zap.Logger
|
|
|
|
db database.Database
|
2022-12-03 15:16:59 +00:00
|
|
|
cancelTimerSub database.CancelFunc
|
2021-11-03 13:48:30 +00:00
|
|
|
}
|
|
|
|
|
2024-03-10 16:38:18 +00:00
|
|
|
func Setup(db database.Database, logger *zap.Logger) *Module {
|
|
|
|
mod := &Module{
|
2022-12-04 13:45:34 +00:00
|
|
|
lastTrigger: sync.NewMap[string, time.Time](),
|
|
|
|
messages: sync.NewSlice[int](),
|
2024-03-10 16:38:18 +00:00
|
|
|
db: db,
|
|
|
|
logger: logger,
|
2022-12-03 23:04:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Fill messages with zero values
|
|
|
|
// (This can probably be done faster)
|
2024-02-25 13:58:35 +00:00
|
|
|
for i := 0; i < AverageMessageWindow; i++ {
|
2022-12-03 23:04:15 +00:00
|
|
|
mod.messages.Push(0)
|
2021-11-03 13:48:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Load config from database
|
2024-03-10 16:38:18 +00:00
|
|
|
err := db.GetJSON(ConfigKey, &mod.Config)
|
2021-11-03 13:48:30 +00:00
|
|
|
if err != nil {
|
2024-03-10 16:38:18 +00:00
|
|
|
logger.Debug("Config load error", zap.Error(err))
|
|
|
|
mod.Config = Config{
|
|
|
|
Timers: make(map[string]ChatTimer),
|
2021-11-03 13:48:30 +00:00
|
|
|
}
|
2021-12-06 11:23:04 +00:00
|
|
|
// Save empty config
|
2024-03-10 16:38:18 +00:00
|
|
|
err = db.PutJSON(ConfigKey, mod.Config)
|
2022-12-02 22:52:45 +00:00
|
|
|
if err != nil {
|
2024-03-10 16:38:18 +00:00
|
|
|
logger.Warn("Could not save default config for bot timers", zap.Error(err))
|
2022-12-02 22:52:45 +00:00
|
|
|
}
|
2021-11-03 13:48:30 +00:00
|
|
|
}
|
|
|
|
|
2024-03-10 16:38:18 +00:00
|
|
|
mod.cancelTimerSub, err = db.SubscribeKey(ConfigKey, func(value string) {
|
2022-11-25 13:16:20 +00:00
|
|
|
err := json.UnmarshalFromString(value, &mod.Config)
|
|
|
|
if err != nil {
|
2024-03-10 16:38:18 +00:00
|
|
|
logger.Debug("Error reloading timer config", zap.Error(err))
|
2022-11-25 13:16:20 +00:00
|
|
|
} else {
|
2024-03-10 16:38:18 +00:00
|
|
|
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 {
|
2024-03-10 16:38:18 +00:00
|
|
|
logger.Error("Could not set-up timer reload subscription", zap.Error(err))
|
2022-11-25 13:16:20 +00:00
|
|
|
}
|
2021-11-24 11:43:55 +00:00
|
|
|
|
2024-03-10 16:38:18 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-03-10 16:38:18 +00:00
|
|
|
func (m *Module) runTimers() {
|
2021-11-03 13:48:30 +00:00
|
|
|
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)
|
|
|
|
|
2024-03-10 16:38:18 +00:00
|
|
|
err := m.db.PutJSON(chat.ActivityKey, m.messages.Get())
|
2022-01-22 12:05:24 +00:00
|
|
|
if err != nil {
|
2024-03-10 16:38:18 +00:00
|
|
|
m.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
|
2023-05-04 13:14:21 +00:00
|
|
|
index := time.Now().Minute() % AverageMessageWindow
|
|
|
|
messages := m.messages.Get()
|
|
|
|
messages[index] = 0
|
|
|
|
m.messages.Set(messages)
|
2021-11-03 13:48:30 +00:00
|
|
|
|
|
|
|
// Run timers
|
2023-05-04 13:14:21 +00:00
|
|
|
for name, timer := range m.Config.Timers {
|
|
|
|
m.ProcessTimer(name, timer, activity)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-10 16:38:18 +00:00
|
|
|
func (m *Module) ProcessTimer(name string, timer ChatTimer, activity int) {
|
2023-05-04 13:14:21 +00:00
|
|
|
// Must be enabled
|
|
|
|
if !timer.Enabled {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if enough time has passed
|
|
|
|
lastTriggeredTime, ok := m.lastTrigger.GetKey(name)
|
|
|
|
if !ok {
|
|
|
|
// If it's the first time we're checking it, start the cooldown
|
|
|
|
lastTriggeredTime = time.Now()
|
|
|
|
m.lastTrigger.SetKey(name, lastTriggeredTime)
|
|
|
|
}
|
|
|
|
|
|
|
|
minDelay := timer.MinimumDelay
|
|
|
|
if minDelay < 60 {
|
|
|
|
minDelay = 60
|
2021-11-03 13:48:30 +00:00
|
|
|
}
|
2023-05-04 13:14:21 +00:00
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
if now.Sub(lastTriggeredTime) < time.Duration(minDelay)*time.Second {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure chat activity is high enough
|
|
|
|
if activity < timer.MinimumChatActivity {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pick a random message
|
|
|
|
message := timer.Messages[rand.Intn(len(timer.Messages))]
|
|
|
|
|
|
|
|
// Write message to chat
|
2024-03-10 16:38:18 +00:00
|
|
|
chat.WriteMessage(m.db, m.logger, chat.WriteMessageRequest{
|
|
|
|
Message: message,
|
|
|
|
Announce: timer.Announce,
|
|
|
|
})
|
2023-05-04 13:14:21 +00:00
|
|
|
|
|
|
|
// Update last trigger
|
|
|
|
m.lastTrigger.SetKey(name, now)
|
2021-11-03 13:48:30 +00:00
|
|
|
}
|
|
|
|
|
2024-03-10 16:38:18 +00:00
|
|
|
func (m *Module) Close() {
|
2022-12-03 15:16:59 +00:00
|
|
|
if m.cancelTimerSub != nil {
|
|
|
|
m.cancelTimerSub()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-10 16:38:18 +00:00
|
|
|
func (m *Module) currentChatActivity() int {
|
2021-11-03 13:48:30 +00:00
|
|
|
total := 0
|
2022-12-03 16:29:19 +00:00
|
|
|
for _, v := range m.messages.Get() {
|
2021-11-03 13:48:30 +00:00
|
|
|
total += v
|
|
|
|
}
|
|
|
|
return total
|
|
|
|
}
|
|
|
|
|
2024-03-10 16:38:18 +00:00
|
|
|
func (m *Module) OnMessage() {
|
|
|
|
index := time.Now().Minute() % AverageMessageWindow
|
2022-12-03 23:04:15 +00:00
|
|
|
m.messages.SetIndex(index, 1)
|
2021-11-03 13:48:30 +00:00
|
|
|
}
|