mirror of
https://git.sr.ht/~ashkeel/strimertul
synced 2024-09-20 02:00:49 +00:00
233 lines
5.7 KiB
Go
233 lines
5.7 KiB
Go
package twitch
|
|
|
|
import (
|
|
"strings"
|
|
"sync"
|
|
"text/template"
|
|
"time"
|
|
|
|
"github.com/strimertul/strimertul/modules/loyalty"
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/Masterminds/sprig/v3"
|
|
irc "github.com/gempir/go-twitch-irc/v2"
|
|
jsoniter "github.com/json-iterator/go"
|
|
)
|
|
|
|
type Bot struct {
|
|
Client *irc.Client
|
|
|
|
api *Client
|
|
username string
|
|
config BotConfig
|
|
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
|
|
|
|
mu sync.Mutex
|
|
|
|
// Module specific vars
|
|
Loyalty *loyalty.Manager
|
|
Timers *BotTimerModule
|
|
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
|
|
}
|
|
bot.mu.Lock()
|
|
bot.activeUsers[message.User.Name] = true
|
|
|
|
lcmessage := strings.ToLower(message.Message)
|
|
|
|
// Check if it's a command
|
|
if strings.HasPrefix(message.Message, "!") {
|
|
// Run through supported commands
|
|
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
|
|
for cmd, data := range bot.customCommands {
|
|
if !data.Enabled {
|
|
continue
|
|
}
|
|
lc := strings.ToLower(cmd)
|
|
if !strings.HasPrefix(lcmessage, lc) {
|
|
continue
|
|
}
|
|
parts := strings.SplitN(lcmessage, " ", 2)
|
|
if parts[0] != lc {
|
|
continue
|
|
}
|
|
go cmdCustom(bot, cmd, data, message)
|
|
bot.lastMessage = time.Now()
|
|
}
|
|
bot.mu.Unlock()
|
|
|
|
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:]
|
|
}
|
|
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 {
|
|
bot.logger.Info("joined channel", zap.String("channel", message.Channel))
|
|
} else {
|
|
bot.logger.Debug("user joined channel", zap.String("channel", message.Channel), zap.String("username", message.User))
|
|
}
|
|
})
|
|
|
|
client.OnUserPartMessage(func(message irc.UserPartMessage) {
|
|
if strings.ToLower(message.User) == bot.username {
|
|
bot.logger.Info("left channel", zap.String("channel", message.Channel))
|
|
} else {
|
|
bot.logger.Debug("user left channel", zap.String("channel", message.Channel), zap.String("username", message.User))
|
|
}
|
|
})
|
|
|
|
bot.Client.Join(config.Channel)
|
|
bot.setupFunctions()
|
|
|
|
// Load modules
|
|
err := bot.LoadModules()
|
|
if err != nil {
|
|
bot.logger.Error("failed to load modules", zap.Error(err))
|
|
}
|
|
|
|
// Load custom commands
|
|
err = api.db.GetJSON(CustomCommandsKey, &bot.customCommands)
|
|
if err != nil {
|
|
bot.logger.Error("failed to load custom commands", zap.Error(err))
|
|
}
|
|
|
|
err = bot.updateTemplates()
|
|
if err != nil {
|
|
bot.logger.Error("failed to parse custom commands", zap.Error(err))
|
|
}
|
|
go api.db.Subscribe(bot.updateCommands, CustomCommandsKey)
|
|
go api.db.Subscribe(bot.handleWriteMessageRPC, WriteMessageRPC)
|
|
|
|
return bot
|
|
}
|
|
|
|
func (b *Bot) updateCommands(key, value string) {
|
|
switch key {
|
|
case CustomCommandsKey:
|
|
err := func() error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
return jsoniter.ConfigFastest.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(key, value string) {
|
|
switch key {
|
|
case WriteMessageRPC:
|
|
b.Client.Say(b.config.Channel, value)
|
|
}
|
|
}
|
|
|
|
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 {
|
|
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
|
|
}
|