diff --git a/botUserState.go b/botUserState.go new file mode 100644 index 0000000..5aa2e51 --- /dev/null +++ b/botUserState.go @@ -0,0 +1,71 @@ +package main + +import ( + "strconv" + "strings" + "sync" + + "github.com/Luzifer/twitch-bot/twitch" + "github.com/go-irc/irc" + "github.com/pkg/errors" +) + +type ( + twitchUserState struct { + Badges twitch.BadgeCollection + Color string + DisplayName string + EmoteSets []int64 + } + + twitchUserStateStore struct { + states map[string]*twitchUserState + lock sync.RWMutex + } +) + +func newTwitchUserStateStore() *twitchUserStateStore { + return &twitchUserStateStore{ + states: make(map[string]*twitchUserState), + } +} + +func parseTwitchUserState(m *irc.Message) (*twitchUserState, error) { + var ( + color, _ = m.GetTag("color") + displayName, _ = m.GetTag("display-name") + emoteSets []int64 + rawSets, _ = m.GetTag("emote-sets") + ) + + if rawSets != "" { + for _, sid := range strings.Split(rawSets, ",") { + id, err := strconv.ParseInt(sid, 10, 64) + if err != nil { + return nil, errors.Wrap(err, "parsing emote-set id") + } + emoteSets = append(emoteSets, id) + } + } + + return &twitchUserState{ + Badges: twitch.ParseBadgeLevels(m), + Color: color, + DisplayName: displayName, + EmoteSets: emoteSets, + }, nil +} + +func (t *twitchUserStateStore) Get(channel string) *twitchUserState { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.states[channel] +} + +func (t *twitchUserStateStore) Set(channel string, state *twitchUserState) { + t.lock.Lock() + defer t.lock.Unlock() + + t.states[channel] = state +} diff --git a/functions_irc.go b/functions_irc.go index f34062a..efbfd54 100644 --- a/functions_irc.go +++ b/functions_irc.go @@ -20,6 +20,12 @@ func init() { } }) + tplFuncs.Register("botHasBadge", func(m *irc.Message, r *plugins.Rule, fields map[string]interface{}) interface{} { + return func(badge string) bool { + return botUserstate.Get(plugins.DeriveChannel(m, nil)).Badges.Has(badge) + } + }) + tplFuncs.Register("fixUsername", plugins.GenericTemplateFunctionGetter(func(username string) string { return strings.TrimLeft(username, "@#") })) tplFuncs.Register("group", func(m *irc.Message, r *plugins.Rule, fields map[string]interface{}) interface{} { diff --git a/irc.go b/irc.go index df5652b..7a7340c 100644 --- a/irc.go +++ b/irc.go @@ -145,6 +145,11 @@ func (i ircHandler) Handle(c *irc.Client, m *irc.Message) { // Announces Twitch-specific events to the channel (for example, a user’s subscription notification). i.handleTwitchUsernotice(m) + case "USERSTATE": + // USERSTATE (Twitch Tags) + // Sends user-state data when a user joins a channel or sends a PRIVMSG to a channel. + i.handleTwitchUserstate(m) + case "WHISPER": // WHISPER (Twitch Commands) // Delivers whisper-messages received @@ -281,6 +286,16 @@ func (i ircHandler) handleTwitchUsernotice(m *irc.Message) { } } +func (i ircHandler) handleTwitchUserstate(m *irc.Message) { + state, err := parseTwitchUserState(m) + if err != nil { + log.WithError(err).Error("Unable to parse bot user-state") + return + } + + botUserstate.Set(plugins.DeriveChannel(m, nil), state) +} + func (i ircHandler) handleTwitchWhisper(m *irc.Message) { go handleMessage(i.c, m, eventTypeWhisper, nil) } diff --git a/main.go b/main.go index d19b6b2..9809792 100644 --- a/main.go +++ b/main.go @@ -40,9 +40,10 @@ var ( config *configFile configLock = new(sync.RWMutex) - cronService *cron.Cron - ircHdl *ircHandler - router = mux.NewRouter() + botUserstate = newTwitchUserStateStore() + cronService *cron.Cron + ircHdl *ircHandler + router = mux.NewRouter() sendMessage func(m *irc.Message) error