diff --git a/events.go b/events.go index a430e09..2e2bd6d 100644 --- a/events.go +++ b/events.go @@ -27,6 +27,7 @@ var ( eventTypeGiftPaidUpgrade = ptrStr("giftpaidupgrade") eventTypeHost = ptrStr("host") eventTypeJoin = ptrStr("join") + eventTypeOutboundRaid = ptrStr("outbound_raid") eventTypePart = ptrStr("part") eventTypePermit = ptrStr("permit") eventTypeRaid = ptrStr("raid") @@ -54,6 +55,7 @@ var ( eventTypeGiftPaidUpgrade, eventTypeHost, eventTypeJoin, + eventTypeOutboundRaid, eventTypePart, eventTypePermit, eventTypeRaid, diff --git a/pkg/twitch/eventsub.go b/pkg/twitch/eventsub.go index 7735b08..91b96cb 100644 --- a/pkg/twitch/eventsub.go +++ b/pkg/twitch/eventsub.go @@ -44,6 +44,7 @@ const ( // eventSubStatusVerificationFailed = "webhook_callback_verification_failed" EventSubEventTypeChannelFollow = "channel.follow" + EventSubEventTypeChannelRaid = "channel.raid" EventSubEventTypeChannelUpdate = "channel.update" EventSubEventTypeStreamOffline = "stream.offline" EventSubEventTypeStreamOnline = "stream.online" @@ -118,6 +119,16 @@ type ( FollowedAt time.Time `json:"followed_at"` } + EventSubEventRaid struct { + FromBroadcasterUserID string `json:"from_broadcaster_user_id"` + FromBroadcasterUserLogin string `json:"from_broadcaster_user_login"` + FromBroadcasterUserName string `json:"from_broadcaster_user_name"` + ToBroadcasterUserID string `json:"to_broadcaster_user_id"` + ToBroadcasterUserLogin string `json:"to_broadcaster_user_login"` + ToBroadcasterUserName string `json:"to_broadcaster_user_name"` + Viewers int64 `json:"viewers"` + } + EventSubEventStreamOffline struct { BroadcasterUserID string `json:"broadcaster_user_id"` BroadcasterUserLogin string `json:"broadcaster_user_login"` diff --git a/twitchWatcher.go b/twitchWatcher.go index 0fd56de..07b2034 100644 --- a/twitchWatcher.go +++ b/twitchWatcher.go @@ -12,6 +12,14 @@ import ( ) type ( + topicRegistration struct { + Topic string + Condition twitch.EventSubCondition + RequiredScopes []string + AnyScope bool + Hook func(json.RawMessage) error + } + twitchChannelState struct { Category string IsLive bool @@ -94,6 +102,48 @@ func (t *twitchWatcher) RemoveChannel(channel string) error { return nil } +func (t *twitchWatcher) getTopicRegistrations(userID string) []topicRegistration { + return []topicRegistration{ + { + Topic: twitch.EventSubEventTypeChannelUpdate, + 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), + }, + { + Topic: twitch.EventSubEventTypeChannelFollow, + Condition: twitch.EventSubCondition{BroadcasterUserID: userID}, + RequiredScopes: nil, + Hook: t.handleEventSubChannelFollow, + }, + { + Topic: twitch.EventSubEventTypeChannelRaid, + Condition: twitch.EventSubCondition{FromBroadcasterUserID: userID}, + RequiredScopes: nil, + Hook: t.handleEventSubChannelOutboundRaid, + }, + { + Topic: twitch.EventSubEventTypeChannelPointCustomRewardRedemptionAdd, + Condition: twitch.EventSubCondition{BroadcasterUserID: userID}, + RequiredScopes: []string{twitch.ScopeChannelReadRedemptions, twitch.ScopeChannelManageRedemptions}, + AnyScope: true, + Hook: t.handleEventSubChannelPointCustomRewardRedemptionAdd, + }, + } +} + func (t *twitchWatcher) handleEventSubChannelFollow(m json.RawMessage) error { var payload twitch.EventSubEventFollow if err := json.Unmarshal(m, &payload); err != nil { @@ -136,6 +186,25 @@ func (t *twitchWatcher) handleEventSubChannelPointCustomRewardRedemptionAdd(m js return nil } +func (t *twitchWatcher) handleEventSubChannelOutboundRaid(m json.RawMessage) error { + var payload twitch.EventSubEventRaid + if err := json.Unmarshal(m, &payload); err != nil { + return errors.Wrap(err, "unmarshalling event") + } + + fields := plugins.FieldCollectionFromData(map[string]interface{}{ + "channel": "#" + payload.FromBroadcasterUserLogin, + "to_id": payload.ToBroadcasterUserID, + "to": payload.ToBroadcasterUserLogin, + "viewers": payload.Viewers, + }) + + log.WithFields(log.Fields(fields.Data())).Info("Outbound raid detected") + go handleMessage(ircHdl.Client(), nil, eventTypeOutboundRaid, fields) + + return nil +} + func (t *twitchWatcher) handleEventSubChannelUpdate(m json.RawMessage) error { var payload twitch.EventSubEventChannelUpdate if err := json.Unmarshal(m, &payload); err != nil { @@ -233,46 +302,8 @@ func (t *twitchWatcher) registerEventSubCallbacks(channel string) (func(), error } var ( - topicRegistrations = []struct { - Topic string - Condition twitch.EventSubCondition - RequiredScopes []string - AnyScope bool - Hook func(json.RawMessage) error - }{ - { - Topic: twitch.EventSubEventTypeChannelUpdate, - 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), - }, - { - Topic: twitch.EventSubEventTypeChannelFollow, - Condition: twitch.EventSubCondition{BroadcasterUserID: userID}, - RequiredScopes: nil, - Hook: t.handleEventSubChannelFollow, - }, - { - Topic: twitch.EventSubEventTypeChannelPointCustomRewardRedemptionAdd, - Condition: twitch.EventSubCondition{BroadcasterUserID: userID}, - RequiredScopes: []string{twitch.ScopeChannelReadRedemptions, twitch.ScopeChannelManageRedemptions}, - AnyScope: true, - Hook: t.handleEventSubChannelPointCustomRewardRedemptionAdd, - }, - } - unsubHandlers []func() + topicRegistrations = t.getTopicRegistrations(userID) + unsubHandlers []func() ) for _, tr := range topicRegistrations { diff --git a/wiki/Events.md b/wiki/Events.md index 67c5f86..c126892 100644 --- a/wiki/Events.md +++ b/wiki/Events.md @@ -98,6 +98,17 @@ Fields: - `channel` - The channel the event occurred in - `user` - The login-name of the user who joined +## `outbound_raid` + +The channel has raided another channel. (The event is issued in the moment the raid is executed, not when the raid timer starts!) + +Fields: + +- `channel` - The channel the raid originated at +- `to` - The login-name of the channel the viewers are sent to +- `to_id` - The ID of the channel the viewers are sent to +- `viewers` - The number of viewers included in the raid + ## `part` User left the channel-chat. This is **NOT** an indicator they are no longer viewing, the event is **NOT** reliably sent when the user really leaves the chat. The event will be sent with some delay after they leave the chat and is sometimes repeated multiple times during their stay. So this does **NOT** mean they do no longer read the chat!