1
0
Fork 0
mirror of https://git.sr.ht/~ashkeel/strimertul synced 2024-09-20 02:00:49 +00:00
strimertul/modules/twitch/modules.timer.go
2021-12-09 13:32:58 +01:00

157 lines
3.9 KiB
Go

package twitch
import (
"context"
"math/rand"
"sync"
"time"
jsoniter "github.com/json-iterator/go"
irc "github.com/gempir/go-twitch-irc/v2"
"github.com/strimertul/strimertul/modules/database"
)
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{
bot: bot,
startTime: time.Now().Round(time.Minute),
lastTrigger: make(map[string]time.Time),
mu: sync.Mutex{},
}
// Load config from database
err := bot.api.db.GetJSON(BotTimersKey, &mod.Config)
if err != nil {
bot.logger.WithError(err).Debug("config load error")
mod.Config = BotTimersConfig{
Timers: make(map[string]BotTimer),
}
// Save empty config
bot.api.db.PutJSON(BotTimersKey, mod.Config)
}
go bot.api.db.Subscribe(context.Background(), func(changed []database.ModifiedKV) error {
for _, kv := range changed {
if kv.Key == BotTimersKey {
err := jsoniter.ConfigFastest.Unmarshal(kv.Data, &mod.Config)
if err != nil {
bot.logger.WithError(err).Debug("error reloading timer config")
} else {
bot.logger.Info("reloaded timer config")
}
}
}
return nil
}, BotTimersKey)
bot.logger.WithField("timers", len(mod.Config.Timers)).Debug("loaded timers")
// 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)
// 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
if !timer.Enabled {
continue
}
// Check if enough time has passed
lastTriggeredTime, ok := m.lastTrigger[name]
if !ok {
// If it's the first time we're checking it, start the cooldown
lastTriggeredTime = time.Now()
m.lastTrigger[name] = lastTriggeredTime
}
minDelay := timer.MinimumDelay
if minDelay < 60 {
minDelay = 60
}
if now.Sub(lastTriggeredTime) < time.Duration(minDelay)*time.Second {
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
}