[core] Add support for Hype-Train events

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2024-02-29 19:00:44 +01:00
parent e50830e509
commit f2ac1acb17
Signed by: luzifer
SSH key fingerprint: SHA256:/xtE5lCgiRDQr8SLxHMS92ZBlACmATUmF1crK16Ks4E
6 changed files with 102 additions and 1 deletions

View file

@ -91,6 +91,17 @@ Fields:
- `gifter` - The login-name of the user who gifted the subscription
- `username` - The login-name of the user who upgraded their subscription
## `hypetrain_begin`, `hypetrain_end`, `hypetrain_progress`
An Hype-Train has begun, ended or progressed in the given channel.
Fields:
- `channel` - The channel the event occurred in
- `level` - The current level of the Hype-Train
- `levelProgress` - Percentage of reached "points" in the current level to complete the level (not available on `hypetrain_end`)
- `event` - Raw Hype-Train event, see schema in [`pkg/twitch/eventsub.go#L92`](https://github.com/Luzifer/twitch-bot/blob/master/pkg/twitch/eventsub.go#L121)
## `join`
User joined the channel-chat. This is **NOT** an indicator they are viewing, the event is **NOT** reliably sent when the user really joined the chat. The event will be sent with some delay after they join the chat and is sometimes repeated multiple times during their stay. So **DO NOT** use this to greet users!
@ -152,7 +163,7 @@ 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)
- `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#L152)
- `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

View file

@ -26,6 +26,9 @@ var (
eventTypeDelete = ptrStr("delete")
eventTypeFollow = ptrStr("follow")
eventTypeGiftPaidUpgrade = ptrStr("giftpaidupgrade")
eventTypeHypetrainBegin = ptrStr("hypetrain_begin")
eventTypeHypetrainEnd = ptrStr("hypetrain_end")
eventTypeHypetrainProgress = ptrStr("hypetrain_progress")
eventTypeJoin = ptrStr("join")
eventKoFiDonation = ptrStr("kofi_donation")
eventTypeOutboundRaid = ptrStr("outbound_raid")

View file

@ -17,6 +17,9 @@ const (
EventSubEventTypeChannelAdBreakBegin = "channel.ad_break.begin"
EventSubEventTypeChannelFollow = "channel.follow"
EventSubEventTypeChannelPointCustomRewardRedemptionAdd = "channel.channel_points_custom_reward_redemption.add"
EventSubEventTypeChannelHypetrainBegin = "channel.hype_train.begin"
EventSubEventTypeChannelHypetrainProgress = "channel.hype_train.progress"
EventSubEventTypeChannelHypetrainEnd = "channel.hype_train.end"
EventSubEventTypeChannelRaid = "channel.raid"
EventSubEventTypeChannelShoutoutCreate = "channel.shoutout.create"
EventSubEventTypeChannelShoutoutReceive = "channel.shoutout.receive"
@ -112,6 +115,38 @@ type (
FollowedAt time.Time `json:"followed_at"`
}
// EventSubEventHypetrain contains the payload for all three (begin,
// progress and end) hypetrain events. Certain fields are not
// available at all event types
EventSubEventHypetrain struct {
ID string `json:"id"`
BroadcasterUserID string `json:"broadcaster_user_id"`
BroadcasterUserLogin string `json:"broadcaster_user_login"`
BroadcasterUserName string `json:"broadcaster_user_name"`
Level int64 `json:"level"`
Total int64 `json:"total"`
Progress int64 `json:"progress"` // Only Beginn, Progress
Goal int64 `json:"goal"` // Only Beginn, Progress
TopContributions []struct {
UserID string `json:"user_id"`
UserLogin string `json:"user_login"`
UserName string `json:"user_name"`
Type string `json:"type"`
Total int64 `json:"total"`
} `json:"top_contributions"`
LastContribution *struct { // Only Begin, Progress
UserID string `json:"user_id"`
UserLogin string `json:"user_login"`
UserName string `json:"user_name"`
Type string `json:"type"`
Total int64 `json:"total"`
} `json:"last_contribution,omitempty"`
StartedAt time.Time `json:"started_at"`
ExpiresAt *time.Time `json:"expires_at,omitempty"` // Only Begin, Progress
EndedAt *time.Time `json:"ended_at,omitempty"` // Only End
CooldownEndsAt *time.Time `json:"cooldown_ends_at,omitempty"` // Only End
}
// EventSubEventPoll contains the payload for a poll change event
// (not all fields are present in all poll events, see docs!)
EventSubEventPoll struct {

View file

@ -15,6 +15,7 @@ const (
ScopeChannelManageVIPS = "channel:manage:vips"
ScopeChannelManageWhispers = "user:manage:whispers"
ScopeChannelReadAds = "channel:read:ads"
ScopeChannelReadHypetrain = "channel:read:hype_train"
ScopeChannelReadPolls = "channel:read:polls"
ScopeChannelReadRedemptions = "channel:read:redemptions"
ScopeChannelReadSubscriptions = "channel:read:subscriptions"

View file

@ -11,6 +11,7 @@ var (
twitch.ScopeChannelManageRaids: "start raids",
twitch.ScopeChannelManageVIPS: "manage VIPs",
twitch.ScopeChannelReadAds: "see when an ad-break starts",
twitch.ScopeChannelReadHypetrain: "see Hype-Train events",
twitch.ScopeChannelReadRedemptions: "see channel-point redemptions",
twitch.ScopeChannelReadSubscriptions: "see subscribed users / sub count / points",
twitch.ScopeClipsEdit: "create clips on behalf of this user",

View file

@ -112,6 +112,7 @@ func (t *twitchWatcher) RemoveChannel(channel string) error {
return nil
}
//nolint:funlen // Just a collection of topics
func (t *twitchWatcher) getTopicRegistrations(userID string) []topicRegistration {
return []topicRegistration{
{
@ -130,6 +131,30 @@ func (t *twitchWatcher) getTopicRegistrations(userID string) []topicRegistration
Hook: t.handleEventSubChannelFollow,
Optional: true,
},
{
Topic: twitch.EventSubEventTypeChannelHypetrainBegin,
Version: twitch.EventSubTopicVersion1,
Condition: twitch.EventSubCondition{BroadcasterUserID: userID},
RequiredScopes: []string{twitch.ScopeChannelReadHypetrain},
Hook: t.handleEventSubHypetrainEvent(eventTypeHypetrainBegin),
Optional: true,
},
{
Topic: twitch.EventSubEventTypeChannelHypetrainEnd,
Version: twitch.EventSubTopicVersion1,
Condition: twitch.EventSubCondition{BroadcasterUserID: userID},
RequiredScopes: []string{twitch.ScopeChannelReadHypetrain},
Hook: t.handleEventSubHypetrainEvent(eventTypeHypetrainEnd),
Optional: true,
},
{
Topic: twitch.EventSubEventTypeChannelHypetrainProgress,
Version: twitch.EventSubTopicVersion1,
Condition: twitch.EventSubCondition{BroadcasterUserID: userID},
RequiredScopes: []string{twitch.ScopeChannelReadHypetrain},
Hook: t.handleEventSubHypetrainEvent(eventTypeHypetrainProgress),
Optional: true,
},
{
Topic: twitch.EventSubEventTypeChannelPointCustomRewardRedemptionAdd,
Condition: twitch.EventSubCondition{BroadcasterUserID: userID},
@ -338,6 +363,31 @@ func (*twitchWatcher) handleEventSubChannelPollChange(event *string) func(json.R
}
}
func (*twitchWatcher) handleEventSubHypetrainEvent(eventType *string) func(json.RawMessage) error {
return func(m json.RawMessage) error {
var payload twitch.EventSubEventHypetrain
if err := json.Unmarshal(m, &payload); err != nil {
return errors.Wrap(err, "unmarshalling event")
}
fields := plugins.FieldCollectionFromData(map[string]any{
"channel": "#" + payload.BroadcasterUserLogin,
"level": payload.Level,
})
if payload.Goal > 0 {
fields.Set("levelProgress", float64(payload.Progress)/float64(payload.Goal))
}
log.WithFields(log.Fields(fields.Data())).Info("Hypetrain event")
fields.Set("event", payload)
go handleMessage(ircHdl.Client(), nil, eventType, fields)
return nil
}
}
func (*twitchWatcher) handleEventSubShoutoutCreated(m json.RawMessage) error {
var payload twitch.EventSubEventShoutoutCreated
if err := json.Unmarshal(m, &payload); err != nil {