diff --git a/cmd/events.go b/cmd/events.go index 09759655..a23a1337 100644 --- a/cmd/events.go +++ b/cmd/events.go @@ -26,6 +26,7 @@ var ( itemID string cost int64 count int + streamTitle string ) var eventCmd = &cobra.Command{ @@ -83,6 +84,7 @@ func init() { triggerCmd.Flags().StringVarP(&status, "status", "S", "", "Status of the event object, currently applies to channel points redemptions.") triggerCmd.Flags().StringVarP(&itemID, "item-id", "i", "", "Manually set the ID of the event payload item (for example the reward ID in redemption events).") triggerCmd.Flags().Int64VarP(&cost, "cost", "C", 0, "Amount of bits or channel points redeemed/used in the event.") + triggerCmd.Flags().StringVarP(&streamTitle, "description", "d", "", "Title the stream should be updated with.") // retrigger flags retriggerCmd.Flags().StringVarP(&forwardAddress, "forward-address", "F", "", "Forward address for mock event.") @@ -125,6 +127,7 @@ func triggerCmdRun(cmd *cobra.Command, args []string) { Status: status, ItemID: itemID, Cost: cost, + StreamTitle: streamTitle, }) if err != nil { diff --git a/internal/events/event.go b/internal/events/event.go index a230b203..8302c682 100644 --- a/internal/events/event.go +++ b/internal/events/event.go @@ -17,6 +17,7 @@ type MockEventParameters struct { Status string ItemID string Cost int64 + StreamTitle string } type MockEventResponse struct { diff --git a/internal/events/trigger/trigger_event.go b/internal/events/trigger/trigger_event.go index 58ce2473..ce10da87 100644 --- a/internal/events/trigger/trigger_event.go +++ b/internal/events/trigger/trigger_event.go @@ -26,6 +26,7 @@ type TriggerParameters struct { Secret string Verbose bool Count int + StreamTitle string } type TriggerResponse struct { @@ -60,6 +61,7 @@ func Fire(p TriggerParameters) (string, error) { IsAnonymous: p.IsAnonymous, Cost: p.Cost, Status: p.Status, + StreamTitle: p.StreamTitle, } e, err := types.GetByTriggerAndTransport(p.Event, p.Transport) diff --git a/internal/events/types/stream_change/stream_change_event.go b/internal/events/types/stream_change/stream_change_event.go new file mode 100644 index 00000000..2b230be1 --- /dev/null +++ b/internal/events/types/stream_change/stream_change_event.go @@ -0,0 +1,123 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package stream_change + +import ( + "encoding/json" + + "github.com/twitchdev/twitch-cli/internal/events" + "github.com/twitchdev/twitch-cli/internal/models" + "github.com/twitchdev/twitch-cli/internal/util" + "time" +) + +var transportsSupported = map[string]bool{ + models.TransportWebSub: true, + models.TransportEventSub: true, +} + +var triggerSupported = []string{"stream_change"} + +var triggerMapping = map[string]map[string]string{ + models.TransportWebSub: { + "stream_change": "streams", + }, + models.TransportEventSub: { + "stream_change": "channel.update", + }, +} + +type Event struct{} + +func (e Event) GenerateEvent(params events.MockEventParameters) (events.MockEventResponse, error) { + var event []byte + var err error + + if params.StreamTitle == "" { + params.StreamTitle = "Default Title!" + } + + switch params.Transport{ + case models.TransportEventSub: + body := &models.EventsubResponse{ + // make the eventsub response (if supported) + Subscription: models.EventsubSubscription{ + ID: params.ID, + Status: "enabled", + Type: "channel.update", + Version: "1", + Condition: models.EventsubCondition{ + BroadcasterUserID: params.ToUserID, + }, + Transport: models.EventsubTransport{ + Method: "webhook", + Callback: "null", + }, + CreatedAt: util.GetTimestamp().Format(time.RFC3339Nano), + }, + Event: models.ChannelUpdateEventSubEvent{ + BroadcasterUserID: params.ToUserID, + BroadcasterUserLogin: params.ToUserID, + BroadcasterUserName: params.ToUserName, + StreamTitle: params.StreamTitle, + StreamLanguage: "en", + StreamCategoryID: "509658", + StreamCategoryName: "Just Chatting", + IsMature: "true", + }, + } + event, err = json.Marshal(body) + if err != nil { + return events.MockEventResponse{}, err + } + case models.TransportWebSub: + body := models.StreamChangeWebSubResponse{ + Data: []models.StreamChangeWebSubResponseData{ + { + WebsubID: params.FromUserID, + BroadcasterUserID: params.ToUserID, + BroadcasterUserLogin: params.ToUserID, + BroadcasterUserName: params.ToUserName, + StreamCategoryID: "509658", + StreamCategoryName: "Just Chatting", + StreamType: "live", + StreamTitle: params.StreamTitle, + StreamViewerCount: 9848, + StreamStartedAt: util.GetTimestamp().Format(time.RFC3339), + StreamLanguage: "en", + StreamThumbnailURL: "https://static-cdn.jtvnw.net/previews-ttv/live_user_lirik-{width}x{height}.jpg", + }, + }, + } + event, err = json.Marshal(body) + if err != nil { + return events.MockEventResponse{}, err + } + default: + return events.MockEventResponse{}, nil + } + + return events.MockEventResponse{ + ID: params.ID, + 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] +} diff --git a/internal/events/types/stream_change/stream_change_event_test.go b/internal/events/types/stream_change/stream_change_event_test.go new file mode 100644 index 00000000..e15c64c2 --- /dev/null +++ b/internal/events/types/stream_change/stream_change_event_test.go @@ -0,0 +1,117 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package stream_change + +import ( + "encoding/json" + "testing" + + "github.com/twitchdev/twitch-cli/internal/events" + "github.com/twitchdev/twitch-cli/internal/models" + "github.com/twitchdev/twitch-cli/internal/util" +) + +var fromUser = "1234" +var toUser = "4567" + +func TestEventSub(t *testing.T) { + a := util.SetupTestEnv(t) + + params := *&events.MockEventParameters{ + FromUserID: fromUser, + ToUserID: toUser, + Transport: models.TransportEventSub, + Trigger: "stream_change", + } + + r, err := Event{}.GenerateEvent(params) + a.Nil(err) + + var body models.ChannelUpdateEventSubResponse + err = json.Unmarshal(r.JSON, &body) + a.Nil(err, "Error unmarshalling JSON") + + // write actual tests here (making sure you set appropriate values and the like) for eventsub + a.Equal(toUser, body.Event.BroadcasterUserID, "Expected Stream Channel %v, got %v", toUser, body.Event.BroadcasterUserID) + + // test for changing a title + params = events.MockEventParameters{ + FromUserID: fromUser, + ToUserID: toUser, + Transport: models.TransportEventSub, + Trigger: "stream_change", + } + + r, err = Event{}.GenerateEvent(params) + a.Nil(err) + + err = json.Unmarshal(r.JSON, &body) + a.Nil(err) + + a.Equal(toUser, body.Event.BroadcasterUserID, "Expected Stream Channel %v, got %v", toUser, body.Event.BroadcasterUserID) + a.Equal("Default Title!", body.Event.StreamTitle, "Expected new stream title, got %v", body.Event.StreamTitle) +} + +func TestWebSubStreamChange(t *testing.T) { + a := util.SetupTestEnv(t) + + newStreamTitle := "Awesome new title!" + + params := *&events.MockEventParameters{ + FromUserID: fromUser, + ToUserID: toUser, + Transport: models.TransportWebSub, + Trigger: "stream_change", + StreamTitle: newStreamTitle, + } + + r, err := Event{}.GenerateEvent(params) + a.Nil(err) + + var body models.StreamChangeWebSubResponse + err = json.Unmarshal(r.JSON, &body) + a.Nil(err) + + // write tests here for websub + a.Equal(toUser, body.Data[0].BroadcasterUserID, "Expected Stream Channel %v, got %v", toUser, body.Data[0].BroadcasterUserID) + a.Equal(newStreamTitle, body.Data[0].StreamTitle, "Expected new stream title, got %v", body.Data[0].StreamTitle) +} +func TestFakeTransport(t *testing.T) { + a := util.SetupTestEnv(t) + + params := *&events.MockEventParameters{ + FromUserID: fromUser, + ToUserID: toUser, + Transport: "fake_transport", + Trigger: "stream_change", + } + + r, err := Event{}.GenerateEvent(params) + a.Nil(err) + a.Empty(r) +} +func TestValidTrigger(t *testing.T) { + a := util.SetupTestEnv(t) + + r := Event{}.ValidTrigger("stream_change") + a.Equal(true, r) + + r = Event{}.ValidTrigger("not_trigger_keyword") + a.Equal(false, r) +} + +func TestValidTransport(t *testing.T) { + a := util.SetupTestEnv(t) + + r := Event{}.ValidTransport(models.TransportEventSub) + a.Equal(true, r) + + r = Event{}.ValidTransport("noteventsub") + a.Equal(false, r) +} +func TestGetTopic(t *testing.T) { + a := util.SetupTestEnv(t) + + r := Event{}.GetTopic(models.TransportEventSub, "stream_change") + a.NotNil(r) +} \ No newline at end of file diff --git a/internal/events/types/types.go b/internal/events/types/types.go index 0867ad79..9b96decd 100644 --- a/internal/events/types/types.go +++ b/internal/events/types/types.go @@ -14,6 +14,7 @@ import ( "github.com/twitchdev/twitch-cli/internal/events/types/follow" "github.com/twitchdev/twitch-cli/internal/events/types/moderator_change" "github.com/twitchdev/twitch-cli/internal/events/types/raid" + "github.com/twitchdev/twitch-cli/internal/events/types/stream_change" "github.com/twitchdev/twitch-cli/internal/events/types/streamdown" "github.com/twitchdev/twitch-cli/internal/events/types/streamup" "github.com/twitchdev/twitch-cli/internal/events/types/subscribe" @@ -29,6 +30,7 @@ func All() []events.MockEvent { follow.Event{}, raid.Event{}, subscribe.Event{}, + stream_change.Event{}, streamup.Event{}, streamdown.Event{}, moderator_change.Event{}, diff --git a/internal/models/stream_change.go b/internal/models/stream_change.go new file mode 100644 index 00000000..ab8d5f31 --- /dev/null +++ b/internal/models/stream_change.go @@ -0,0 +1,39 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package models + +type ChannelUpdateEventSubEvent struct { + BroadcasterUserID string `json:"broadcaster_user_id"` + BroadcasterUserLogin string `json:"broadcaster_user_login"` + BroadcasterUserName string `json:"broadcaster_user_name"` + StreamTitle string `json:"title"` + StreamLanguage string `json:"language"` + StreamCategoryID string `json:"category_id"` + StreamCategoryName string `json:"category_name"` + IsMature string `json:"is_mature"` +} + +type StreamChangeWebSubResponse struct { + Data []StreamChangeWebSubResponseData `json:"data"` +} + +type StreamChangeWebSubResponseData struct { + WebsubID string `json:"id"` + BroadcasterUserID string `json:"user_id"` + BroadcasterUserLogin string `json:"user_login"` + BroadcasterUserName string `json:"user_name"` + StreamCategoryID string `json:"game_id"` + StreamCategoryName string `json:"game_name"` + StreamType string `json:"type"` + StreamTitle string `json:"title"` + StreamViewerCount int `json:"viewer_count"` + StreamStartedAt string `json:"started_at"` + StreamLanguage string `json:"language"` + StreamThumbnailURL string `json:"thumbnail_url"` + TagIDs []string `json:"tag_ids"` +} + +type ChannelUpdateEventSubResponse struct { + Subscription EventsubSubscription `json:"subscription"` + Event ChannelUpdateEventSubEvent `json:"event"` +}