2021-09-02 15:09:30 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2021-11-08 19:17:07 +00:00
|
|
|
"encoding/json"
|
2021-09-02 15:09:30 +00:00
|
|
|
"sync"
|
|
|
|
|
2021-09-02 21:26:39 +00:00
|
|
|
"github.com/Luzifer/twitch-bot/plugins"
|
2021-11-08 19:17:07 +00:00
|
|
|
"github.com/Luzifer/twitch-bot/twitch"
|
2021-09-02 15:09:30 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
twitchChannelState struct {
|
|
|
|
Category string
|
|
|
|
IsLive bool
|
|
|
|
Title string
|
2021-11-08 19:17:07 +00:00
|
|
|
|
|
|
|
unregisterFunc func()
|
2021-09-02 15:09:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
twitchWatcher struct {
|
|
|
|
ChannelStatus map[string]*twitchChannelState
|
|
|
|
|
|
|
|
lock sync.RWMutex
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
func (t twitchChannelState) Equals(c twitchChannelState) bool {
|
|
|
|
return t.Category == c.Category &&
|
|
|
|
t.IsLive == c.IsLive &&
|
|
|
|
t.Title == c.Title
|
|
|
|
}
|
|
|
|
|
|
|
|
func newTwitchWatcher() *twitchWatcher {
|
|
|
|
return &twitchWatcher{
|
|
|
|
ChannelStatus: make(map[string]*twitchChannelState),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-08 19:17:07 +00:00
|
|
|
func (t *twitchWatcher) AddChannel(channel string) error {
|
|
|
|
t.lock.RLock()
|
|
|
|
_, ok := t.ChannelStatus[channel]
|
|
|
|
t.lock.RUnlock()
|
|
|
|
|
|
|
|
if ok {
|
2021-09-02 15:09:30 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-11-08 19:17:07 +00:00
|
|
|
return t.updateChannelFromAPI(channel, false)
|
2021-09-02 15:09:30 +00:00
|
|
|
}
|
|
|
|
|
2021-11-08 19:17:07 +00:00
|
|
|
func (t *twitchWatcher) Check() {
|
2021-09-02 15:09:30 +00:00
|
|
|
var channels []string
|
2021-11-08 19:17:07 +00:00
|
|
|
t.lock.RLock()
|
|
|
|
for c := range t.ChannelStatus {
|
|
|
|
if t.ChannelStatus[c].unregisterFunc != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-09-02 15:09:30 +00:00
|
|
|
channels = append(channels, c)
|
|
|
|
}
|
2021-11-08 19:17:07 +00:00
|
|
|
t.lock.RUnlock()
|
2021-09-02 15:09:30 +00:00
|
|
|
|
|
|
|
for _, ch := range channels {
|
2021-11-08 19:17:07 +00:00
|
|
|
if err := t.updateChannelFromAPI(ch, true); err != nil {
|
2021-09-02 15:09:30 +00:00
|
|
|
log.WithError(err).WithField("channel", ch).Error("Unable to update channel status")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-08 19:17:07 +00:00
|
|
|
func (t *twitchWatcher) RemoveChannel(channel string) error {
|
|
|
|
t.lock.Lock()
|
|
|
|
defer t.lock.Unlock()
|
|
|
|
|
|
|
|
if f := t.ChannelStatus[channel].unregisterFunc; f != nil {
|
|
|
|
f()
|
|
|
|
}
|
2021-09-02 15:09:30 +00:00
|
|
|
|
2021-11-08 19:17:07 +00:00
|
|
|
delete(t.ChannelStatus, channel)
|
2021-09-02 15:09:30 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-11-08 19:17:07 +00:00
|
|
|
func (t *twitchWatcher) updateChannelFromAPI(channel string, sendUpdate bool) error {
|
2021-09-02 15:09:30 +00:00
|
|
|
var (
|
|
|
|
err error
|
|
|
|
status twitchChannelState
|
|
|
|
)
|
|
|
|
|
|
|
|
status.IsLive, err = twitchClient.HasLiveStream(channel)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "getting live status")
|
|
|
|
}
|
|
|
|
|
|
|
|
status.Category, status.Title, err = twitchClient.GetRecentStreamInfo(channel)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "getting stream info")
|
|
|
|
}
|
|
|
|
|
2021-11-08 19:17:07 +00:00
|
|
|
t.lock.Lock()
|
|
|
|
defer t.lock.Unlock()
|
2021-09-02 15:09:30 +00:00
|
|
|
|
2021-11-08 19:17:07 +00:00
|
|
|
if t.ChannelStatus[channel] != nil && t.ChannelStatus[channel].Equals(status) {
|
2021-09-02 15:09:30 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-11-08 19:17:07 +00:00
|
|
|
if sendUpdate && t.ChannelStatus[channel] != nil {
|
|
|
|
t.triggerUpdate(channel, &status.Title, &status.Category, &status.IsLive)
|
|
|
|
return nil
|
|
|
|
}
|
2021-09-02 15:39:21 +00:00
|
|
|
|
2021-11-08 19:17:07 +00:00
|
|
|
if status.unregisterFunc, err = t.registerEventSubCallbacks(channel); err != nil {
|
|
|
|
return errors.Wrap(err, "registering eventsub callbacks")
|
|
|
|
}
|
|
|
|
|
|
|
|
t.ChannelStatus[channel] = &status
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *twitchWatcher) registerEventSubCallbacks(channel string) (func(), error) {
|
|
|
|
if twitchEventSubClient == nil {
|
|
|
|
// We don't have eventsub functionality
|
|
|
|
return nil, nil
|
|
|
|
}
|
2021-09-02 15:09:30 +00:00
|
|
|
|
2021-11-08 19:17:07 +00:00
|
|
|
userID, err := twitchClient.GetIDForUsername(channel)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "resolving channel to user-id")
|
|
|
|
}
|
2021-09-02 15:09:30 +00:00
|
|
|
|
2021-11-08 19:17:07 +00:00
|
|
|
unsubCU, err := twitchEventSubClient.RegisterEventSubHooks(
|
|
|
|
twitch.EventSubEventTypeChannelUpdate,
|
|
|
|
twitch.EventSubCondition{BroadcasterUserID: userID},
|
|
|
|
func(m json.RawMessage) error {
|
|
|
|
var payload twitch.EventSubEventChannelUpdate
|
|
|
|
if err := json.Unmarshal(m, &payload); err != nil {
|
|
|
|
return errors.Wrap(err, "unmarshalling event")
|
2021-09-02 15:40:38 +00:00
|
|
|
}
|
2021-09-02 15:33:48 +00:00
|
|
|
|
2021-11-08 19:17:07 +00:00
|
|
|
t.triggerUpdate(channel, &payload.Title, &payload.CategoryName, nil)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "registering channel-update eventsub")
|
2021-09-02 15:09:30 +00:00
|
|
|
}
|
|
|
|
|
2021-11-08 19:17:07 +00:00
|
|
|
unsubSOff, err := twitchEventSubClient.RegisterEventSubHooks(
|
|
|
|
twitch.EventSubEventTypeStreamOffline,
|
|
|
|
twitch.EventSubCondition{BroadcasterUserID: userID},
|
|
|
|
func(m json.RawMessage) error {
|
|
|
|
var payload twitch.EventSubEventStreamOffline
|
|
|
|
if err := json.Unmarshal(m, &payload); err != nil {
|
|
|
|
return errors.Wrap(err, "unmarshalling event")
|
|
|
|
}
|
|
|
|
|
|
|
|
t.triggerUpdate(channel, nil, nil, func(v bool) *bool { return &v }(false))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "registering channel-update eventsub")
|
|
|
|
}
|
|
|
|
|
|
|
|
unsubSOn, err := twitchEventSubClient.RegisterEventSubHooks(
|
|
|
|
twitch.EventSubEventTypeStreamOnline,
|
|
|
|
twitch.EventSubCondition{BroadcasterUserID: userID},
|
|
|
|
func(m json.RawMessage) error {
|
|
|
|
var payload twitch.EventSubEventStreamOnline
|
|
|
|
if err := json.Unmarshal(m, &payload); err != nil {
|
|
|
|
return errors.Wrap(err, "unmarshalling event")
|
|
|
|
}
|
|
|
|
|
|
|
|
t.triggerUpdate(channel, nil, nil, func(v bool) *bool { return &v }(true))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "registering channel-update eventsub")
|
|
|
|
}
|
|
|
|
|
|
|
|
return func() {
|
|
|
|
unsubCU()
|
|
|
|
unsubSOff()
|
|
|
|
unsubSOn()
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *twitchWatcher) triggerUpdate(channel string, title, category *string, online *bool) {
|
|
|
|
if category != nil && t.ChannelStatus[channel].Category != *category {
|
|
|
|
t.ChannelStatus[channel].Category = *category
|
|
|
|
log.WithFields(log.Fields{
|
|
|
|
"channel": channel,
|
|
|
|
"category": *category,
|
|
|
|
}).Debug("Twitch metadata changed")
|
2021-11-11 13:59:08 +00:00
|
|
|
go handleMessage(ircHdl.Client(), nil, eventTypeTwitchCategoryUpdate, plugins.FieldCollectionFromData(map[string]interface{}{
|
2021-11-08 19:17:07 +00:00
|
|
|
"channel": channel,
|
|
|
|
"category": *category,
|
2021-11-11 13:59:08 +00:00
|
|
|
}))
|
2021-11-08 19:17:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if title != nil && t.ChannelStatus[channel].Title != *title {
|
|
|
|
t.ChannelStatus[channel].Title = *title
|
|
|
|
log.WithFields(log.Fields{
|
|
|
|
"channel": channel,
|
|
|
|
"title": *title,
|
|
|
|
}).Debug("Twitch metadata changed")
|
2021-11-11 13:59:08 +00:00
|
|
|
go handleMessage(ircHdl.Client(), nil, eventTypeTwitchTitleUpdate, plugins.FieldCollectionFromData(map[string]interface{}{
|
2021-11-08 19:17:07 +00:00
|
|
|
"channel": channel,
|
|
|
|
"title": *title,
|
2021-11-11 13:59:08 +00:00
|
|
|
}))
|
2021-11-08 19:17:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if online != nil && t.ChannelStatus[channel].IsLive != *online {
|
|
|
|
t.ChannelStatus[channel].IsLive = *online
|
|
|
|
log.WithFields(log.Fields{
|
|
|
|
"channel": channel,
|
|
|
|
"isLive": *online,
|
|
|
|
}).Debug("Twitch metadata changed")
|
|
|
|
|
|
|
|
evt := eventTypeTwitchStreamOnline
|
|
|
|
if !*online {
|
|
|
|
evt = eventTypeTwitchStreamOffline
|
|
|
|
}
|
|
|
|
|
2021-11-11 13:59:08 +00:00
|
|
|
go handleMessage(ircHdl.Client(), nil, evt, plugins.FieldCollectionFromData(map[string]interface{}{
|
2021-11-08 19:17:07 +00:00
|
|
|
"channel": channel,
|
2021-11-11 13:59:08 +00:00
|
|
|
}))
|
2021-11-08 19:17:07 +00:00
|
|
|
}
|
2021-09-02 15:09:30 +00:00
|
|
|
}
|