diff --git a/events.go b/events.go index d7e77a8..c4e5519 100644 --- a/events.go +++ b/events.go @@ -32,6 +32,7 @@ var ( eventTypePermit = ptrStr("permit") eventTypeRaid = ptrStr("raid") eventTypeResub = ptrStr("resub") + eventTypeShoutoutReceived = ptrStr("shoutout_received") eventTypeSubgift = ptrStr("subgift") eventTypeSubmysterygift = ptrStr("submysterygift") eventTypeSub = ptrStr("sub") @@ -60,6 +61,7 @@ var ( eventTypePermit, eventTypeRaid, eventTypeResub, + eventTypeShoutoutReceived, eventTypeSub, eventTypeSubgift, eventTypeSubmysterygift, diff --git a/pkg/twitch/eventsub.go b/pkg/twitch/eventsub.go index 7c8ddd2..9bc5d77 100644 --- a/pkg/twitch/eventsub.go +++ b/pkg/twitch/eventsub.go @@ -43,15 +43,14 @@ const ( // eventSubStatusUserRemoved = "user_removed" // eventSubStatusVerificationFailed = "webhook_callback_verification_failed" - EventSubEventTypeChannelFollow = "channel.follow" - EventSubEventTypeChannelRaid = "channel.raid" - EventSubEventTypeChannelUpdate = "channel.update" - EventSubEventTypeStreamOffline = "stream.offline" - EventSubEventTypeStreamOnline = "stream.online" - + EventSubEventTypeChannelFollow = "channel.follow" EventSubEventTypeChannelPointCustomRewardRedemptionAdd = "channel.channel_points_custom_reward_redemption.add" - - EventSubEventTypeUserAuthorizationRevoke = "user.authorization.revoke" + EventSubEventTypeChannelRaid = "channel.raid" + EventSubEventTypeChannelShoutoutReceive = "channel.shoutout.receive" + EventSubEventTypeChannelUpdate = "channel.update" + EventSubEventTypeStreamOffline = "stream.offline" + EventSubEventTypeStreamOnline = "stream.online" + EventSubEventTypeUserAuthorizationRevoke = "user.authorization.revoke" EventSubTopicVersion1 = "1" EventSubTopicVersion2 = "2" @@ -81,6 +80,7 @@ type ( RewardID string `json:"reward_id,omitempty"` ToBroadcasterUserID string `json:"to_broadcaster_user_id,omitempty"` UserID string `json:"user_id,omitempty"` + ModeratorUserID string `json:"moderator_user_id,omitempty"` } EventSubEventChannelPointCustomRewardRedemptionAdd struct { @@ -133,6 +133,17 @@ type ( Viewers int64 `json:"viewers"` } + EventSubEventShoutoutReceived struct { + BroadcasterUserID string `json:"broadcaster_user_id"` + BroadcasterUserLogin string `json:"broadcaster_user_login"` + BroadcasterUserName string `json:"broadcaster_user_name"` + FromBroadcasterUserID string `json:"from_broadcaster_user_id"` + FromBroadcasterUserLogin string `json:"from_broadcaster_user_login"` + FromBroadcasterUserName string `json:"from_broadcaster_user_name"` + ViewerCount int64 `json:"viewer_count"` + StartedAt time.Time `json:"started_at"` + } + EventSubEventStreamOffline struct { BroadcasterUserID string `json:"broadcaster_user_id"` BroadcasterUserLogin string `json:"broadcaster_user_login"` diff --git a/pkg/twitch/scopes.go b/pkg/twitch/scopes.go index bc7fa75..e81f451 100644 --- a/pkg/twitch/scopes.go +++ b/pkg/twitch/scopes.go @@ -19,6 +19,7 @@ const ( ScopeModeratorManageShieldMode = "moderator:manage:shield_mode" ScopeModeratorManageShoutouts = "moderator:manage:shoutouts" ScopeModeratorReadFollowers = "moderator:read:followers" + ScopeModeratorReadShoutouts = "moderator:read:shoutouts" ScopeUserManageChatColor = "user:manage:chat_color" // Deprecated v5 scope but used in chat diff --git a/scopes.go b/scopes.go index 3b83dcc..a93b6b7 100644 --- a/scopes.go +++ b/scopes.go @@ -12,6 +12,7 @@ var ( twitch.ScopeChannelManageVIPS: "manage VIPs", twitch.ScopeChannelReadRedemptions: "see channel-point redemptions", twitch.ScopeModeratorReadFollowers: "see who follows this channel", + twitch.ScopeModeratorReadShoutouts: "see shoutouts received", } botDefaultScopes = []string{ diff --git a/twitchWatcher.go b/twitchWatcher.go index 2ec788c..e652019 100644 --- a/twitchWatcher.go +++ b/twitchWatcher.go @@ -143,6 +143,13 @@ func (t *twitchWatcher) getTopicRegistrations(userID string) []topicRegistration AnyScope: true, Hook: t.handleEventSubChannelPointCustomRewardRedemptionAdd, }, + { + Topic: twitch.EventSubEventTypeChannelShoutoutReceive, + Condition: twitch.EventSubCondition{BroadcasterUserID: userID, ModeratorUserID: userID}, + RequiredScopes: []string{twitch.ScopeModeratorManageShoutouts, twitch.ScopeModeratorReadShoutouts}, + AnyScope: true, + Hook: t.handleEventSubShoutoutReceived, + }, } } @@ -218,6 +225,25 @@ func (t *twitchWatcher) handleEventSubChannelUpdate(m json.RawMessage) error { return nil } +func (t *twitchWatcher) handleEventSubShoutoutReceived(m json.RawMessage) error { + var payload twitch.EventSubEventShoutoutReceived + if err := json.Unmarshal(m, &payload); err != nil { + return errors.Wrap(err, "unmarshalling event") + } + + fields := plugins.FieldCollectionFromData(map[string]any{ + "channel": "#" + payload.FromBroadcasterUserLogin, + "from_id": payload.FromBroadcasterUserID, + "from": payload.FromBroadcasterUserLogin, + "viewers": payload.ViewerCount, + }) + + log.WithFields(log.Fields(fields.Data())).Info("Shoutout received") + go handleMessage(ircHdl.Client(), nil, eventTypeShoutoutReceived, fields) + + return nil +} + func (t *twitchWatcher) handleEventSubStreamOnOff(isOnline bool) func(json.RawMessage) error { return func(m json.RawMessage) error { var payload twitch.EventSubEventFollow diff --git a/wiki/Events.md b/wiki/Events.md index c126892..1533440 100644 --- a/wiki/Events.md +++ b/wiki/Events.md @@ -149,6 +149,17 @@ Fields: - `subscribed_months` - How long have they been subscribed - `username` - The login-name of the user who resubscribed +## `shoutout_received` + +The channel received a (Twitch native) shoutout by another channel. + +Fields: + +- `channel` - The channel the event occurred in +- `from_id` - The ID of the channel who issued the shoutout +- `from` - The login-name of the channel who issued the shoutout +- `viewers` - The amount of viewers the shoutout was shown to + ## `stream_offline` The channels stream went offline. (This event has some delay to the real category change!)