1
0
Fork 0
mirror of https://git.sr.ht/~ashkeel/strimertul synced 2024-09-18 01:50:50 +00:00
strimertul/modules/twitch/bot.go

232 lines
5.7 KiB
Go
Raw Normal View History

package twitch
import (
"strings"
2021-06-05 23:18:31 +00:00
"sync"
"text/template"
"time"
"github.com/strimertul/strimertul/modules/loyalty"
2022-01-27 15:49:18 +00:00
"go.uber.org/zap"
2021-12-06 11:22:06 +00:00
"github.com/Masterminds/sprig/v3"
2022-03-24 09:16:51 +00:00
irc "github.com/gempir/go-twitch-irc/v3"
)
type Bot struct {
Client *irc.Client
api *Client
username string
config BotConfig
2022-01-27 15:49:18 +00:00
logger *zap.Logger
lastMessage time.Time
activeUsers map[string]bool
banlist map[string]bool
chatHistory []irc.PrivateMessage
commands map[string]BotCommand
customCommands map[string]BotCustomCommand
customTemplates map[string]*template.Template
customFunctions template.FuncMap
2021-09-17 08:54:55 +00:00
2021-06-05 23:18:31 +00:00
mu sync.Mutex
// Module specific vars
Loyalty *loyalty.Manager
Timers *BotTimerModule
2021-12-05 14:47:53 +00:00
Alerts *BotAlertsModule
}
func NewBot(api *Client, config BotConfig) *Bot {
// Create client
client := irc.NewClient(config.Username, config.Token)
bot := &Bot{
Client: client,
username: strings.ToLower(config.Username), // Normalize username
config: config,
logger: api.logger,
api: api,
lastMessage: time.Now(),
activeUsers: make(map[string]bool),
banlist: make(map[string]bool),
mu: sync.Mutex{},
commands: make(map[string]BotCommand),
customCommands: make(map[string]BotCustomCommand),
customTemplates: make(map[string]*template.Template),
}
client.OnPrivateMessage(func(message irc.PrivateMessage) {
// Ignore messages for a while or twitch will get mad!
if message.Time.Before(bot.lastMessage.Add(time.Second * 2)) {
bot.logger.Debug("message received too soon, ignoring")
return
}
2021-06-05 23:18:31 +00:00
bot.mu.Lock()
bot.activeUsers[message.User.Name] = true
2021-10-05 11:06:56 +00:00
lcmessage := strings.ToLower(message.Message)
// Check if it's a command
if strings.HasPrefix(message.Message, "!") {
// Run through supported commands
2021-09-17 08:54:55 +00:00
for cmd, data := range bot.commands {
if !data.Enabled {
continue
}
if !strings.HasPrefix(lcmessage, cmd) {
continue
}
parts := strings.SplitN(lcmessage, " ", 2)
if parts[0] != cmd {
continue
}
go data.Handler(bot, message)
bot.lastMessage = time.Now()
}
}
// Run through custom commands
2021-09-17 08:54:55 +00:00
for cmd, data := range bot.customCommands {
if !data.Enabled {
continue
}
2021-10-05 11:06:56 +00:00
lc := strings.ToLower(cmd)
2021-12-01 22:32:51 +00:00
if !strings.HasPrefix(lcmessage, lc) {
continue
}
parts := strings.SplitN(lcmessage, " ", 2)
if parts[0] != lc {
continue
}
2021-12-01 22:32:51 +00:00
go cmdCustom(bot, cmd, data, message)
bot.lastMessage = time.Now()
}
2021-09-17 08:54:55 +00:00
bot.mu.Unlock()
2022-01-21 09:08:51 +00:00
bot.api.db.PutJSON(ChatEventKey, message)
if bot.config.ChatHistory > 0 {
if len(bot.chatHistory) >= bot.config.ChatHistory {
bot.chatHistory = bot.chatHistory[len(bot.chatHistory)-bot.config.ChatHistory+1:]
}
2022-01-21 09:08:51 +00:00
bot.chatHistory = append(bot.chatHistory, message)
bot.api.db.PutJSON(ChatHistoryKey, bot.chatHistory)
}
if bot.Timers != nil {
go bot.Timers.OnMessage(message)
}
})
client.OnUserJoinMessage(func(message irc.UserJoinMessage) {
if strings.ToLower(message.User) == bot.username {
2022-01-27 15:49:18 +00:00
bot.logger.Info("joined channel", zap.String("channel", message.Channel))
} else {
2022-01-27 15:49:18 +00:00
bot.logger.Debug("user joined channel", zap.String("channel", message.Channel), zap.String("username", message.User))
}
})
2021-06-05 23:18:31 +00:00
client.OnUserPartMessage(func(message irc.UserPartMessage) {
if strings.ToLower(message.User) == bot.username {
2022-01-27 15:49:18 +00:00
bot.logger.Info("left channel", zap.String("channel", message.Channel))
} else {
2022-01-27 15:49:18 +00:00
bot.logger.Debug("user left channel", zap.String("channel", message.Channel), zap.String("username", message.User))
}
})
bot.Client.Join(config.Channel)
2022-01-14 15:24:27 +00:00
bot.setupFunctions()
// Load modules
err := bot.LoadModules()
if err != nil {
2022-01-27 15:49:18 +00:00
bot.logger.Error("failed to load modules", zap.Error(err))
}
2021-09-17 08:54:55 +00:00
// Load custom commands
2022-01-21 09:08:51 +00:00
err = api.db.GetJSON(CustomCommandsKey, &bot.customCommands)
if err != nil {
2022-01-27 15:49:18 +00:00
bot.logger.Error("failed to load custom commands", zap.Error(err))
}
2022-01-21 09:08:51 +00:00
err = bot.updateTemplates()
if err != nil {
2022-01-27 15:49:18 +00:00
bot.logger.Error("failed to parse custom commands", zap.Error(err))
2022-01-21 09:08:51 +00:00
}
err = api.db.SubscribeKey(CustomCommandsKey, bot.updateCommands)
if err != nil {
bot.logger.Error("could not set-up bot command reload subscription", zap.Error(err))
}
err = api.db.SubscribeKey(WriteMessageRPC, bot.handleWriteMessageRPC)
if err != nil {
bot.logger.Error("could not set-up bot command reload subscription", zap.Error(err))
}
2021-09-17 08:54:55 +00:00
return bot
}
func (b *Bot) updateCommands(value string) {
err := func() error {
b.mu.Lock()
defer b.mu.Unlock()
return json.UnmarshalFromString(value, &b.customCommands)
}()
if err != nil {
b.logger.Error("failed to decode new custom commands", zap.Error(err))
return
}
// Recreate templates
if err := b.updateTemplates(); err != nil {
b.logger.Error("failed to update custom commands templates", zap.Error(err))
return
}
}
func (b *Bot) handleWriteMessageRPC(value string) {
b.Client.Say(b.config.Channel, value)
2021-11-12 22:23:30 +00:00
}
func (b *Bot) updateTemplates() error {
for cmd, tmpl := range b.customCommands {
var err error
b.customTemplates[cmd], err = template.New("").Funcs(sprig.TxtFuncMap()).Funcs(b.customFunctions).Parse(tmpl.Response)
if err != nil {
2021-09-17 08:54:55 +00:00
return err
}
}
return nil
}
func (b *Bot) Connect() error {
return b.Client.Connect()
}
func (b *Bot) WriteMessage(message string) {
b.Client.Say(b.config.Channel, message)
}
func getUserAccessLevel(user irc.User) AccessLevelType {
// Check broadcaster
if _, ok := user.Badges["broadcaster"]; ok {
return ALTStreamer
}
// Check mods
if _, ok := user.Badges["moderator"]; ok {
return ALTModerators
}
// Check VIP
if _, ok := user.Badges["vip"]; ok {
return ALTVIP
}
// Check subscribers
if _, ok := user.Badges["subscriber"]; ok {
return ALTSubscribers
}
return ALTEveryone
}