From a9e4931271110b2b71e13b30f25b389b72a76b6f Mon Sep 17 00:00:00 2001 From: Knut Ahlers Date: Sat, 21 Oct 2023 16:43:44 +0200 Subject: [PATCH] Add support for beta Ad-Break event Signed-off-by: Knut Ahlers --- events.go | 2 ++ pkg/twitch/eventsub.go | 11 +++++++ pkg/twitch/scopes.go | 7 ++++- scopes.go | 1 + twitchWatcher.go | 70 +++++++++++++++++++++++++++++------------- 5 files changed, 68 insertions(+), 23 deletions(-) diff --git a/events.go b/events.go index 96ba7af..2d97cbb 100644 --- a/events.go +++ b/events.go @@ -16,6 +16,7 @@ var ( ) var ( + eventTypeAdBreakBegin = ptrStr("adbreak_begin") eventTypeAnnouncement = ptrStr("announcement") eventTypeBan = ptrStr("ban") eventTypeBits = ptrStr("bits") @@ -49,6 +50,7 @@ var ( eventTypeTwitchTitleUpdate = ptrStr("title_update") knownEvents = []*string{ + eventTypeAdBreakBegin, eventTypeAnnouncement, eventTypeBan, eventTypeBits, diff --git a/pkg/twitch/eventsub.go b/pkg/twitch/eventsub.go index 26a3e04..52b5706 100644 --- a/pkg/twitch/eventsub.go +++ b/pkg/twitch/eventsub.go @@ -16,6 +16,7 @@ import ( ) const ( + EventSubEventTypeChannelAdBreakBegin = "channel.ad_break.begin" EventSubEventTypeChannelFollow = "channel.follow" EventSubEventTypeChannelPointCustomRewardRedemptionAdd = "channel.channel_points_custom_reward_redemption.add" EventSubEventTypeChannelRaid = "channel.raid" @@ -49,6 +50,16 @@ type ( ModeratorUserID string `json:"moderator_user_id,omitempty"` } + EventSubEventAdBreakBegin struct { + Duration int64 `json:"duration"` + Timestamp time.Time `json:"timestamp"` + IsAutomatic bool `json:"is_automatic"` + BroadcasterUserID string `json:"broadcaster_user_id"` + BroadcasterUserLogin string `json:"broadcaster_user_login"` + BroadcasterUserName string `json:"broadcaster_user_name"` + RequesterUserID string `json:"requester_user_id"` + } + EventSubEventChannelPointCustomRewardRedemptionAdd struct { ID string `json:"id"` BroadcasterUserID string `json:"broadcaster_user_id"` diff --git a/pkg/twitch/scopes.go b/pkg/twitch/scopes.go index 1b7378a..92b4048 100644 --- a/pkg/twitch/scopes.go +++ b/pkg/twitch/scopes.go @@ -2,7 +2,9 @@ package twitch const ( // API Scopes + ScopeChannelBot = "channel:bot" ScopeChannelEditCommercial = "channel:edit:commercial" + ScopeChannelManageAds = "channel:manage:ads" ScopeChannelManageBroadcast = "channel:manage:broadcast" ScopeChannelManageModerators = "channel:manage:moderators" ScopeChannelManagePolls = "channel:manage:polls" @@ -10,11 +12,12 @@ const ( ScopeChannelManageRaids = "channel:manage:raids" ScopeChannelManageRedemptions = "channel:manage:redemptions" ScopeChannelManageVIPS = "channel:manage:vips" - ScopeClipsEdit = "clips:edit" ScopeChannelManageWhispers = "user:manage:whispers" + ScopeChannelReadAds = "channel:read:ads" ScopeChannelReadPolls = "channel:read:polls" ScopeChannelReadRedemptions = "channel:read:redemptions" ScopeChannelReadSubscriptions = "channel:read:subscriptions" + ScopeClipsEdit = "clips:edit" ScopeModeratorManageAnnoucements = "moderator:manage:announcements" ScopeModeratorManageBannedUsers = "moderator:manage:banned_users" ScopeModeratorManageChatMessages = "moderator:manage:chat_messages" @@ -23,8 +26,10 @@ const ( ScopeModeratorManageShoutouts = "moderator:manage:shoutouts" ScopeModeratorReadFollowers = "moderator:read:followers" ScopeModeratorReadShoutouts = "moderator:read:shoutouts" + ScopeUserBot = "user:bot" ScopeUserManageChatColor = "user:manage:chat_color" ScopeUserManageWhispers = "user:manage:whispers" + ScopeUserReadChat = "user:read:chat" // Deprecated v5 scope but used in chat ScopeV5ChannelEditor = "channel_editor" diff --git a/scopes.go b/scopes.go index 5978161..a89a8d4 100644 --- a/scopes.go +++ b/scopes.go @@ -10,6 +10,7 @@ var ( twitch.ScopeChannelManagePredictions: "manage predictions", twitch.ScopeChannelManageRaids: "start raids", twitch.ScopeChannelManageVIPS: "manage VIPs", + twitch.ScopeChannelReadAds: "see when an ad-break starts", twitch.ScopeChannelReadRedemptions: "see channel-point redemptions", twitch.ScopeChannelReadSubscriptions: "see subscribed users / sub count / points", twitch.ScopeClipsEdit: "create clips on behalf of this user", diff --git a/twitchWatcher.go b/twitchWatcher.go index 4356a18..24043e9 100644 --- a/twitchWatcher.go +++ b/twitchWatcher.go @@ -113,23 +113,11 @@ func (t *twitchWatcher) RemoveChannel(channel string) error { func (t *twitchWatcher) getTopicRegistrations(userID string) []topicRegistration { return []topicRegistration{ { - Topic: twitch.EventSubEventTypeChannelUpdate, - Version: twitch.EventSubTopicVersion2, + Topic: twitch.EventSubEventTypeChannelAdBreakBegin, + Version: twitch.EventSubTopicVersionBeta, Condition: twitch.EventSubCondition{BroadcasterUserID: userID}, - RequiredScopes: nil, - Hook: t.handleEventSubChannelUpdate, - }, - { - Topic: twitch.EventSubEventTypeStreamOffline, - Condition: twitch.EventSubCondition{BroadcasterUserID: userID}, - RequiredScopes: nil, - Hook: t.handleEventSubStreamOnOff(false), - }, - { - Topic: twitch.EventSubEventTypeStreamOnline, - Condition: twitch.EventSubCondition{BroadcasterUserID: userID}, - RequiredScopes: nil, - Hook: t.handleEventSubStreamOnOff(true), + RequiredScopes: []string{twitch.ScopeChannelReadAds}, + Hook: t.handleEventSubChannelAdBreakBegin, }, { Topic: twitch.EventSubEventTypeChannelFollow, @@ -138,12 +126,6 @@ func (t *twitchWatcher) getTopicRegistrations(userID string) []topicRegistration RequiredScopes: []string{twitch.ScopeModeratorReadFollowers}, Hook: t.handleEventSubChannelFollow, }, - { - Topic: twitch.EventSubEventTypeChannelRaid, - Condition: twitch.EventSubCondition{FromBroadcasterUserID: userID}, - RequiredScopes: nil, - Hook: t.handleEventSubChannelOutboundRaid, - }, { Topic: twitch.EventSubEventTypeChannelPointCustomRewardRedemptionAdd, Condition: twitch.EventSubCondition{BroadcasterUserID: userID}, @@ -172,6 +154,12 @@ func (t *twitchWatcher) getTopicRegistrations(userID string) []topicRegistration AnyScope: true, Hook: t.handleEventSubChannelPollChange(eventTypePollProgress), }, + { + Topic: twitch.EventSubEventTypeChannelRaid, + Condition: twitch.EventSubCondition{FromBroadcasterUserID: userID}, + RequiredScopes: nil, + Hook: t.handleEventSubChannelOutboundRaid, + }, { Topic: twitch.EventSubEventTypeChannelShoutoutCreate, Condition: twitch.EventSubCondition{BroadcasterUserID: userID, ModeratorUserID: userID}, @@ -186,9 +174,47 @@ func (t *twitchWatcher) getTopicRegistrations(userID string) []topicRegistration AnyScope: true, Hook: t.handleEventSubShoutoutReceived, }, + { + Topic: twitch.EventSubEventTypeChannelUpdate, + Version: twitch.EventSubTopicVersion2, + Condition: twitch.EventSubCondition{BroadcasterUserID: userID}, + RequiredScopes: nil, + Hook: t.handleEventSubChannelUpdate, + }, + { + Topic: twitch.EventSubEventTypeStreamOffline, + Condition: twitch.EventSubCondition{BroadcasterUserID: userID}, + RequiredScopes: nil, + Hook: t.handleEventSubStreamOnOff(false), + }, + { + Topic: twitch.EventSubEventTypeStreamOnline, + Condition: twitch.EventSubCondition{BroadcasterUserID: userID}, + RequiredScopes: nil, + Hook: t.handleEventSubStreamOnOff(true), + }, } } +func (t *twitchWatcher) handleEventSubChannelAdBreakBegin(m json.RawMessage) error { + var payload twitch.EventSubEventAdBreakBegin + if err := json.Unmarshal(m, &payload); err != nil { + return errors.Wrap(err, "unmarshalling event") + } + + fields := plugins.FieldCollectionFromData(map[string]any{ + "channel": "#" + payload.BroadcasterUserLogin, + "duration": payload.Duration, + "is_automatic": payload.IsAutomatic, + "timestamp": payload.Timestamp, + }) + + log.WithFields(log.Fields(fields.Data())).Info("Ad-Break started") + go handleMessage(ircHdl.Client(), nil, eventTypeAdBreakBegin, fields) + + return nil +} + func (t *twitchWatcher) handleEventSubChannelFollow(m json.RawMessage) error { var payload twitch.EventSubEventFollow if err := json.Unmarshal(m, &payload); err != nil {