diff --git a/events.go b/events.go index c627f71..8cf81aa 100644 --- a/events.go +++ b/events.go @@ -29,6 +29,9 @@ var ( eventTypeOutboundRaid = ptrStr("outbound_raid") eventTypePart = ptrStr("part") eventTypePermit = ptrStr("permit") + eventTypePollBegin = ptrStr("poll_begin") + eventTypePollEnd = ptrStr("poll_end") + eventTypePollProgress = ptrStr("poll_progress") eventTypeRaid = ptrStr("raid") eventTypeResub = ptrStr("resub") eventTypeShoutoutCreated = ptrStr("shoutout_created") @@ -58,6 +61,9 @@ var ( eventTypeOutboundRaid, eventTypePart, eventTypePermit, + eventTypePollBegin, + eventTypePollEnd, + eventTypePollProgress, eventTypeRaid, eventTypeResub, eventTypeShoutoutReceived, diff --git a/pkg/twitch/eventsub.go b/pkg/twitch/eventsub.go index 544c062..8e8bd84 100644 --- a/pkg/twitch/eventsub.go +++ b/pkg/twitch/eventsub.go @@ -22,6 +22,9 @@ const ( EventSubEventTypeChannelShoutoutCreate = "channel.shoutout.create" EventSubEventTypeChannelShoutoutReceive = "channel.shoutout.receive" EventSubEventTypeChannelUpdate = "channel.update" + EventSubEventTypeChannelPollBegin = "channel.poll.begin" + EventSubEventTypeChannelPollEnd = "channel.poll.end" + EventSubEventTypeChannelPollProgress = "channel.poll.progress" EventSubEventTypeStreamOffline = "stream.offline" EventSubEventTypeStreamOnline = "stream.online" EventSubEventTypeUserAuthorizationRevoke = "user.authorization.revoke" @@ -86,6 +89,29 @@ type ( FollowedAt time.Time `json:"followed_at"` } + EventSubEventPoll struct { + ID string `json:"id"` + BroadcasterUserID string `json:"broadcaster_user_id"` + BroadcasterUserLogin string `json:"broadcaster_user_login"` + BroadcasterUserName string `json:"broadcaster_user_name"` + Title string `json:"title"` + Choices []struct { + ID string `json:"id"` + Title string `json:"title"` + ChannelPointsVotes int `json:"channel_points_votes"` + Votes int `json:"votes"` + } `json:"choices"` + ChannelPointsVoting struct { + IsEnabled bool `json:"is_enabled"` + AmountPerVote int `json:"amount_per_vote"` + } `json:"channel_points_voting"` + + StartedAt time.Time `json:"started_at"` // begin, progress, end + EndsAt time.Time `json:"ends_at,omitempty"` // begin, progress + Status string `json:"status,omitempty"` // end -- enum(completed, archived, terminated) + EndedAt time.Time `json:"ended_at,omitempty"` // end + } + EventSubEventRaid struct { FromBroadcasterUserID string `json:"from_broadcaster_user_id"` FromBroadcasterUserLogin string `json:"from_broadcaster_user_login"` diff --git a/pkg/twitch/scopes.go b/pkg/twitch/scopes.go index be09ad6..4620a07 100644 --- a/pkg/twitch/scopes.go +++ b/pkg/twitch/scopes.go @@ -11,6 +11,7 @@ const ( ScopeChannelManageRedemptions = "channel:manage:redemptions" ScopeChannelManageVIPS = "channel:manage:vips" ScopeChannelManageWhispers = "user:manage:whispers" + ScopeChannelReadPolls = "channel:read:polls" ScopeChannelReadRedemptions = "channel:read:redemptions" ScopeChannelReadSubscriptions = "channel:read:subscriptions" ScopeModeratorManageAnnoucements = "moderator:manage:announcements" diff --git a/twitchWatcher.go b/twitchWatcher.go index 47b7d4f..582b3aa 100644 --- a/twitchWatcher.go +++ b/twitchWatcher.go @@ -149,6 +149,27 @@ func (t *twitchWatcher) getTopicRegistrations(userID string) []topicRegistration AnyScope: true, Hook: t.handleEventSubChannelPointCustomRewardRedemptionAdd, }, + { + Topic: twitch.EventSubEventTypeChannelPollBegin, + Condition: twitch.EventSubCondition{BroadcasterUserID: userID}, + RequiredScopes: []string{twitch.ScopeChannelReadPolls, twitch.ScopeChannelManagePolls}, + AnyScope: true, + Hook: t.handleEventSubChannelPollChange(eventTypePollBegin), + }, + { + Topic: twitch.EventSubEventTypeChannelPollEnd, + Condition: twitch.EventSubCondition{BroadcasterUserID: userID}, + RequiredScopes: []string{twitch.ScopeChannelReadPolls, twitch.ScopeChannelManagePolls}, + AnyScope: true, + Hook: t.handleEventSubChannelPollChange(eventTypePollEnd), + }, + { + Topic: twitch.EventSubEventTypeChannelPollProgress, + Condition: twitch.EventSubCondition{BroadcasterUserID: userID}, + RequiredScopes: []string{twitch.ScopeChannelReadPolls, twitch.ScopeChannelManagePolls}, + AnyScope: true, + Hook: t.handleEventSubChannelPollChange(eventTypePollProgress), + }, { Topic: twitch.EventSubEventTypeChannelShoutoutCreate, Condition: twitch.EventSubCondition{BroadcasterUserID: userID, ModeratorUserID: userID}, @@ -238,6 +259,42 @@ func (t *twitchWatcher) handleEventSubChannelUpdate(m json.RawMessage) error { return nil } +func (t *twitchWatcher) handleEventSubChannelPollChange(event *string) func(json.RawMessage) error { + return func(m json.RawMessage) error { + var payload twitch.EventSubEventPoll + if err := json.Unmarshal(m, &payload); err != nil { + return errors.Wrap(err, "unmarshalling event") + } + + fields := plugins.FieldCollectionFromData(map[string]any{ + "channel": "#" + payload.BroadcasterUserLogin, + "hasChannelPointVoting": payload.ChannelPointsVoting.IsEnabled, + "title": payload.Title, + }) + + logger := log.WithFields(log.Fields(fields.Data())) + + switch event { + case eventTypePollBegin: + logger.Info("Poll started") + + case eventTypePollEnd: + logger.WithField("status", payload.Status).Info("Poll ended") + + case eventTypePollProgress: + // Lets not spam the info-level-log with every single vote but + // provide them for bots with debug-level-logging + logger.Debug("Poll changed") + } + + // Set after logging not to spam logs with full payload + fields.Set("poll", payload) + + go handleMessage(ircHdl.Client(), nil, event, fields) + return nil + } +} + func (t *twitchWatcher) handleEventSubShoutoutCreated(m json.RawMessage) error { var payload twitch.EventSubEventShoutoutCreated if err := json.Unmarshal(m, &payload); err != nil { diff --git a/wiki/Events.md b/wiki/Events.md index c92dc4a..3568020 100644 --- a/wiki/Events.md +++ b/wiki/Events.md @@ -128,6 +128,17 @@ Fields: - `user` - The login-name of the user who **gave** the permit - `to` - The username who got the permit +## `poll_begin` / `poll_end` / `poll_progress` + +A poll was started / was ended / had changes in the given channel. + +Fields: + +- `channel` - The channel the event occurred in +- `poll` - The poll object describing the poll, see schema in [`pkg/twitch/eventsub.go#L92`](https://github.com/Luzifer/twitch-bot/blob/master/pkg/twitch/eventsub.go#L92) +- `status` - The status of the poll (one of `completed`, `terminated` or `archived`) - only available in `poll_end` +- `title` - The title of the poll the event was generated for + ## `raid` The channel was raided by another user.