Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/event.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ This command can take either the Event or Alias listed as an argument. It is pre
| `channel.goal.begin` | `goal-begin` | Channel creator goal start event. |
| `channel.goal.end` | `goal-end` | Channel creator goal end event. |
| `channel.goal.progress` | `goal-progress` | Channel creator goal progress event. |
| `channel.hype_train.begin` | `hype-train-begin` | Channel hype train start event. |
| `channel.hype_train.end` | `hype-train-end` | Channel hype train start event. |
| `channel.hype_train.progress` | `hype-train-progress` | Channel hype train start event. |
| `channel.hype_train.begin` | `hype-train-begin` | Channel hype train begin event. Supports V1 and V2; use `--version 2` for V2 with shared train and type fields. |
| `channel.hype_train.end` | `hype-train-end` | Channel hype train end event. Supports V1 and V2; use `--version 2` for V2 with shared train and type fields. |
| `channel.hype_train.progress` | `hype-train-progress` | Channel hype train progress event. Supports V1 and V2; use `--version 2` for V2 with shared train and type fields. |
| `channel.moderator.add` | `add-moderator` | Channel moderator add event. |
| `channel.moderator.remove` | `remove-moderator` | Channel moderator removal event. |
| `channel.poll.begin` | `poll-begin` | Channel poll begin event. |
Expand Down
172 changes: 172 additions & 0 deletions internal/events/types/hype_train_v2/hype_train_event.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package hype_train_v2

import (
"encoding/json"
"strings"
"time"

"github.com/twitchdev/twitch-cli/internal/events"
"github.com/twitchdev/twitch-cli/internal/models"
"github.com/twitchdev/twitch-cli/internal/util"
)

var transportsSupported = map[string]bool{
models.TransportWebhook: true,
models.TransportWebSocket: true,
}
var triggerSupported = []string{"hype-train-begin", "hype-train-progress", "hype-train-end"}
var triggerMapping = map[string]map[string]string{
models.TransportWebhook: {
"hype-train-progress": "channel.hype_train.progress",
"hype-train-begin": "channel.hype_train.begin",
"hype-train-end": "channel.hype_train.end",
},
models.TransportWebSocket: {
"hype-train-progress": "channel.hype_train.progress",
"hype-train-begin": "channel.hype_train.begin",
"hype-train-end": "channel.hype_train.end",
},
}

type Event struct{}

func (e Event) GenerateEvent(params events.MockEventParameters) (events.MockEventResponse, error) {
var event []byte
var err error

localLevel := util.RandomInt(4) + 1
localTotal := util.RandomInt(10 * 100)
localGoal := util.RandomInt(10*100*100) + localTotal
localProgress := localTotal - util.RandomInt(100)

tNow, _ := time.Parse(time.RFC3339Nano, params.Timestamp)

switch params.Transport {
case models.TransportWebhook, models.TransportWebSocket:
body := models.HypeTrainEventSubResponseV2{
Subscription: models.EventsubSubscription{
ID: params.SubscriptionID,
Status: params.SubscriptionStatus,
Type: triggerMapping[params.Transport][params.Trigger],
Version: e.SubscriptionVersion(),
Condition: models.EventsubCondition{
BroadcasterUserID: params.ToUserID,
},
Transport: models.EventsubTransport{
Method: "webhook",
Callback: "null",
},
Cost: 0,
CreatedAt: params.Timestamp,
},
Event: models.HypeTrainEventSubEventV2{
ID: util.RandomGUID(),
BroadcasterUserID: params.ToUserID,
BroadcasterUserLogin: params.ToUserName,
BroadcasterUserName: params.ToUserName,
Total: localTotal,
Progress: &localProgress,
Goal: localGoal,
TopContributions: []models.ContributionData{
{
TotalContribution: util.RandomInt(10 * 100),
TypeOfContribution: util.RandomType(),
UserWhoMadeContribution: util.RandomUserID(),
UserNameWhoMadeContribution: "cli_user1",
UserLoginWhoMadeContribution: "cli_user1",
},
{
TotalContribution: util.RandomInt(10 * 100),
TypeOfContribution: util.RandomType(),
UserWhoMadeContribution: util.RandomUserID(),
UserNameWhoMadeContribution: "cli_user2",
UserLoginWhoMadeContribution: "cli_user2",
},
},
SharedTrainParticipants: nil,
Level: localLevel,
StartedAtTimestamp: params.Timestamp,
ExpiresAtTimestamp: tNow.Add(5 * time.Minute).Format(time.RFC3339Nano),
IsSharedTrain: false,
Type: "regular",
},
}
if params.Trigger == "hype-train-begin" {
body.Event.Progress = &localTotal
body.Event.AllTimeHighLevel = localLevel + util.RandomInt(3)
body.Event.AllTimeHighTotal = localTotal + util.RandomInt(10*100)
}
if params.Trigger == "hype-train-end" {
body.Event.CooldownEndsAtTimestamp = tNow.Add(1 * time.Hour).Format(time.RFC3339Nano)
body.Event.EndedAtTimestamp = tNow.Format(time.RFC3339Nano)
body.Event.ExpiresAtTimestamp = ""
body.Event.Goal = 0
body.Event.Progress = nil
body.Event.StartedAtTimestamp = tNow.Add(5 * -time.Minute).Format(time.RFC3339Nano)
}
event, err = json.Marshal(body)
if err != nil {
return events.MockEventResponse{}, err
}

// Delete event info if Subscription.Status is not set to "enabled"
if !strings.EqualFold(params.SubscriptionStatus, "enabled") {
var i interface{}
if err := json.Unmarshal([]byte(event), &i); err != nil {
return events.MockEventResponse{}, err
}
if m, ok := i.(map[string]interface{}); ok {
delete(m, "event") // Matches JSON key defined in body variable above
}

event, err = json.Marshal(i)
if err != nil {
return events.MockEventResponse{}, err
}
}
default:
return events.MockEventResponse{}, nil
}
return events.MockEventResponse{
ID: params.EventMessageID,
JSON: event,
FromUser: params.FromUserID,
ToUser: params.ToUserID,
}, nil
}
func (e Event) ValidTransport(t string) bool {
return transportsSupported[t]
}
func (e Event) ValidTrigger(t string) bool {
for _, ts := range triggerSupported {
if ts == t {
return true
}
}
return false
}
func (e Event) GetTopic(transport string, trigger string) string {
return triggerMapping[transport][trigger]
}
func (e Event) GetAllTopicsByTransport(transport string) []string {
allTopics := []string{}
for _, topic := range triggerMapping[transport] {
allTopics = append(allTopics, topic)
}
return allTopics
}
func (e Event) GetEventSubAlias(t string) string {
// check for aliases
for trigger, topic := range triggerMapping[models.TransportWebhook] {
if topic == t {
return trigger
}
}
return ""
}

func (e Event) SubscriptionVersion() string {
return "2"
}
147 changes: 147 additions & 0 deletions internal/events/types/hype_train_v2/hype_train_event_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package hype_train_v2

import (
"encoding/json"
"testing"

"github.com/twitchdev/twitch-cli/internal/events"
"github.com/twitchdev/twitch-cli/internal/models"
"github.com/twitchdev/twitch-cli/test_setup"
)

var toUser = "4567"

func TestEventSub(t *testing.T) {
a := test_setup.SetupTestEnv(t)

params := events.MockEventParameters{
ToUserID: toUser,
Transport: models.TransportWebhook,
Trigger: "hype-train-begin",
SubscriptionStatus: "enabled",
}

r, err := Event{}.GenerateEvent(params)
a.Nil(err)

var body models.HypeTrainEventSubResponseV2
err = json.Unmarshal(r.JSON, &body)
a.Nil(err)

a.Equal("channel.hype_train.begin", body.Subscription.Type, "Expected event type %v, got %v", "channel.hype_train.begin", body.Subscription.Type)
a.Equal(toUser, body.Event.BroadcasterUserID, "Expected to user %v, got %v", toUser, body.Event.BroadcasterUserID)
a.Equal("2", body.Subscription.Version, "Expected version 2, got %v", body.Subscription.Version)
a.Equal("regular", body.Event.Type, "Expected type regular, got %v", body.Event.Type)
a.Equal(false, body.Event.IsSharedTrain, "Expected is_shared_train false")
a.Nil(body.Event.SharedTrainParticipants, "Expected shared_train_participants nil")
a.NotZero(body.Event.AllTimeHighLevel, "Expected all_time_high_level to be set for begin event")
a.NotZero(body.Event.AllTimeHighTotal, "Expected all_time_high_total to be set for begin event")
a.NotNil(body.Event.Progress, "Expected progress to be set for begin event")

params = events.MockEventParameters{
ToUserID: toUser,
Transport: models.TransportWebhook,
Trigger: "hype-train-progress",
SubscriptionStatus: "enabled",
}

r, err = Event{}.GenerateEvent(params)
a.Nil(err)

body = models.HypeTrainEventSubResponseV2{}
err = json.Unmarshal(r.JSON, &body)
a.Nil(err)

a.Equal("channel.hype_train.progress", body.Subscription.Type, "Expected event type %v, got %v", "channel.hype_train.progress", body.Subscription.Type)
a.Equal(toUser, body.Event.BroadcasterUserID, "Expected to user %v, got %v", toUser, body.Event.BroadcasterUserID)
a.Equal("2", body.Subscription.Version)
a.NotNil(body.Event.Progress, "Expected progress to be set for progress event")

params = events.MockEventParameters{
ToUserID: toUser,
Transport: models.TransportWebhook,
Trigger: "hype-train-end",
SubscriptionStatus: "enabled",
}

r, err = Event{}.GenerateEvent(params)
a.Nil(err)

body = models.HypeTrainEventSubResponseV2{}
err = json.Unmarshal(r.JSON, &body)
a.Nil(err)

a.Equal("channel.hype_train.end", body.Subscription.Type, "Expected event type %v, got %v", "channel.hype_train.end", body.Subscription.Type)
a.Equal(toUser, body.Event.BroadcasterUserID, "Expected to user %v, got %v", toUser, body.Event.BroadcasterUserID)
a.Equal("2", body.Subscription.Version)
a.Nil(body.Event.Progress, "Expected progress to be nil for end event")
a.NotEmpty(body.Event.EndedAtTimestamp, "Expected ended_at to be set for end event")
a.NotEmpty(body.Event.CooldownEndsAtTimestamp, "Expected cooldown_ends_at to be set for end event")
}

func TestWebSocketTransport(t *testing.T) {
a := test_setup.SetupTestEnv(t)

params := events.MockEventParameters{
ToUserID: toUser,
Transport: models.TransportWebSocket,
Trigger: "hype-train-begin",
SubscriptionStatus: "enabled",
}

r, err := Event{}.GenerateEvent(params)
a.Nil(err)

var body models.HypeTrainEventSubResponseV2
err = json.Unmarshal(r.JSON, &body)
a.Nil(err)

a.Equal("channel.hype_train.begin", body.Subscription.Type)
a.Equal("2", body.Subscription.Version)
}

func TestFakeTransport(t *testing.T) {
a := test_setup.SetupTestEnv(t)

params := events.MockEventParameters{
ToUserID: toUser,
Transport: "fake_transport",
Trigger: "hype-train-progress",
}

r, err := Event{}.GenerateEvent(params)
a.Nil(err)
a.Empty(r)
}
func TestValidTrigger(t *testing.T) {
a := test_setup.SetupTestEnv(t)

r := Event{}.ValidTrigger("hype-train-begin")
a.Equal(true, r)

r = Event{}.ValidTrigger("hype-train-progress")
a.Equal(true, r)

r = Event{}.ValidTrigger("hype-train-end")
a.Equal(true, r)

}

func TestValidTransport(t *testing.T) {
a := test_setup.SetupTestEnv(t)

r := Event{}.ValidTransport(models.TransportWebhook)
a.Equal(true, r)

r = Event{}.ValidTransport(models.TransportWebSocket)
a.Equal(true, r)
}

func TestGetTopic(t *testing.T) {
a := test_setup.SetupTestEnv(t)

r := Event{}.GetTopic(models.TransportWebhook, "hype-train-progress")
a.Equal("channel.hype_train.progress", r, "Expected %v, got %v", "channel.hype_train.progress", r)
}
7 changes: 6 additions & 1 deletion internal/events/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/twitchdev/twitch-cli/internal/events/types/gift"
"github.com/twitchdev/twitch-cli/internal/events/types/goal"
"github.com/twitchdev/twitch-cli/internal/events/types/hype_train"
"github.com/twitchdev/twitch-cli/internal/events/types/hype_train_v2"
"github.com/twitchdev/twitch-cli/internal/events/types/moderator_change"
"github.com/twitchdev/twitch-cli/internal/events/types/poll"
"github.com/twitchdev/twitch-cli/internal/events/types/prediction"
Expand Down Expand Up @@ -57,6 +58,7 @@ func AllEvents() []events.MockEvent {
gift.Event{},
goal.Event{},
hype_train.Event{},
hype_train_v2.Event{},
moderator_change.Event{},
poll.Event{},
prediction.Event{},
Expand Down Expand Up @@ -160,6 +162,9 @@ func GetByTriggerAndTransportAndVersion(trigger string, transport string, versio
// This does not include any "beta" events, just old production versions
func RemovedEvents() map[string]string {
return map[string]string{
"channel.follow": "1",
"channel.follow": "1",
"channel.hype_train.begin": "1",
"channel.hype_train.end": "1",
"channel.hype_train.progress": "1",
}
}
32 changes: 32 additions & 0 deletions internal/models/hype_train.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,35 @@ type HypeTrainEventSubEvent struct {
EndedAtTimestamp string `json:"ended_at,omitempty"`
CooldownEndsAtTimestamp string `json:"cooldown_ends_at,omitempty"`
}

type SharedTrainParticipant struct {
BroadcasterUserID string `json:"broadcaster_user_id"`
BroadcasterUserLogin string `json:"broadcaster_user_login"`
BroadcasterUserName string `json:"broadcaster_user_name"`
}

type HypeTrainEventSubEventV2 struct {
ID string `json:"id"`
BroadcasterUserID string `json:"broadcaster_user_id"`
BroadcasterUserLogin string `json:"broadcaster_user_login"`
BroadcasterUserName string `json:"broadcaster_user_name"`
Total int64 `json:"total"`
Progress *int64 `json:"progress,omitempty"`
Goal int64 `json:"goal,omitempty"`
TopContributions []ContributionData `json:"top_contributions"`
SharedTrainParticipants *[]SharedTrainParticipant `json:"shared_train_participants"`
Level int64 `json:"level,omitempty"`
StartedAtTimestamp string `json:"started_at,omitempty"`
ExpiresAtTimestamp string `json:"expires_at,omitempty"`
EndedAtTimestamp string `json:"ended_at,omitempty"`
CooldownEndsAtTimestamp string `json:"cooldown_ends_at,omitempty"`
IsSharedTrain bool `json:"is_shared_train"`
Type string `json:"type"`
AllTimeHighLevel int64 `json:"all_time_high_level,omitempty"`
AllTimeHighTotal int64 `json:"all_time_high_total,omitempty"`
}

type HypeTrainEventSubResponseV2 struct {
Subscription EventsubSubscription `json:"subscription"`
Event HypeTrainEventSubEventV2 `json:"event"`
}