diff --git a/internal/events/models.go b/internal/events/models.go index c43ae867..88062a00 100644 --- a/internal/events/models.go +++ b/internal/events/models.go @@ -35,6 +35,8 @@ var triggerSupported = map[string]bool{ "revoke": true, "shield-mode-begin": true, "shield-mode-end": true, + "shoutout-create": true, + "shoutout-received": true, "stream-change": true, "streamdown": true, "streamup": true, diff --git a/internal/events/types/shoutout/shoutout.go b/internal/events/types/shoutout/shoutout.go new file mode 100644 index 00000000..3621faea --- /dev/null +++ b/internal/events/types/shoutout/shoutout.go @@ -0,0 +1,169 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package shoutout + +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.TransportEventSub: true, +} +var triggers = []string{"shoutout-create", "shoutout-received"} + +var triggerMapping = map[string]map[string]string{ + models.TransportEventSub: { + "shoutout-create": "channel.shoutout.create", + "shoutout-received": "channel.shoutout.receive", + }, +} + +type Event struct{} + +func (e Event) GenerateEvent(params events.MockEventParameters) (events.MockEventResponse, error) { + var event []byte + var err error + + switch params.Transport { + case models.TransportEventSub: + viewerCount := util.RandomInt(2000) + startedAt := util.GetTimestamp() + + moderatorUserID := "3502151007" + + if params.Trigger == "shoutout-create" { + body := models.ShoutoutCreateEventSubResponse{ + Subscription: models.EventsubSubscription{ + ID: params.ID, + Status: params.SubscriptionStatus, + Type: triggerMapping[params.Transport][params.Trigger], + Version: e.SubscriptionVersion(), + Condition: models.EventsubCondition{ + BroadcasterUserID: params.FromUserID, + ModeratorUserID: moderatorUserID, + }, + Transport: models.EventsubTransport{ + Method: "webhook", + Callback: "null", + }, + Cost: 0, + CreatedAt: params.Timestamp, + }, + Event: models.ShoutoutCreateEventSubEvent{ + BroadcasterUserID: params.FromUserID, + BroadcasterUserName: params.FromUserName, + BroadcasterUserLogin: params.FromUserName, + ToBroadcasterUserID: params.ToUserID, + ToBroadcasterUserName: params.ToUserName, + ToBroadcasterUserLogin: params.ToUserName, + ModeratorUserID: moderatorUserID, + ModeratorUserName: "TrustedUser123", + ModeratorUserLogin: "trusteduser123", + ViewerCount: int(viewerCount), + StartedAt: startedAt.Format(time.RFC3339Nano), + CooldownEndsAt: startedAt.Add(2 * time.Minute).Format(time.RFC3339Nano), + TargetCooldownEndsAt: startedAt.Add(1 * time.Hour).Format(time.RFC3339Nano), + }, + } + + event, err = json.Marshal(body) + if err != nil { + return events.MockEventResponse{}, err + } + } else if params.Trigger == "shoutout-received" { + body := models.ShoutoutReceivedEventSubResponse{ + Subscription: models.EventsubSubscription{ + ID: params.ID, + Status: params.SubscriptionStatus, + Type: triggerMapping[params.Transport][params.Trigger], + Version: e.SubscriptionVersion(), + Condition: models.EventsubCondition{ + BroadcasterUserID: params.ToUserID, + ModeratorUserID: moderatorUserID, + }, + Transport: models.EventsubTransport{ + Method: "webhook", + Callback: "null", + }, + Cost: 0, + CreatedAt: params.Timestamp, + }, + Event: models.ShoutoutReceivedEventSubEvent{ + BroadcasterUserID: params.ToUserID, + BroadcasterUserName: params.ToUserName, + BroadcasterUserLogin: params.ToUserName, + FromBroadcasterUserID: params.FromUserID, + FromBroadcasterUserName: params.FromUserName, + FromBroadcasterUserLogin: params.FromUserName, + ViewerCount: int(viewerCount), + StartedAt: startedAt.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.ID, + JSON: event, + ToUser: params.ToUserID, + FromUser: params.FromUserID, + }, nil +} + +func (e Event) ValidTransport(transport string) bool { + return transportsSupported[transport] +} + +func (e Event) ValidTrigger(trigger string) bool { + for _, t := range triggers { + if t == trigger { + return true + } + } + return false +} +func (e Event) GetTopic(transport string, trigger string) string { + return triggerMapping[transport][trigger] +} +func (e Event) GetEventSubAlias(t string) string { + // check for aliases + for trigger, topic := range triggerMapping[models.TransportEventSub] { + if topic == t { + return trigger + } + } + return "" +} + +func (e Event) SubscriptionVersion() string { + return "beta" +} diff --git a/internal/events/types/shoutout/shoutout_test.go b/internal/events/types/shoutout/shoutout_test.go new file mode 100644 index 00000000..b2407fa1 --- /dev/null +++ b/internal/events/types/shoutout/shoutout_test.go @@ -0,0 +1,107 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package shoutout + +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 fromUser = "1234" +var toUser = "4567" + +func TestEventSub(t *testing.T) { + a := test_setup.SetupTestEnv(t) + + beginParams := *&events.MockEventParameters{ + FromUserID: fromUser, + ToUserID: toUser, + Transport: models.TransportEventSub, + Trigger: "shoutout-create", + SubscriptionStatus: "enabled", + Cost: 0, + } + endParams := *&events.MockEventParameters{ + FromUserID: fromUser, + ToUserID: toUser, + Transport: models.TransportEventSub, + Trigger: "shoutout-received", + SubscriptionStatus: "enabled", + Cost: 0, + } + + r1, err := Event{}.GenerateEvent(beginParams) + a.Nil(err) + + r2, err := Event{}.GenerateEvent(endParams) + a.Nil(err) + + var body1 models.ShoutoutCreateEventSubResponse + err = json.Unmarshal(r1.JSON, &body1) + a.Nil(err) + + var body2 models.ShoutoutReceivedEventSubResponse + err = json.Unmarshal(r2.JSON, &body2) + a.Nil(err) +} + +func TestFakeTransport(t *testing.T) { + a := test_setup.SetupTestEnv(t) + + beginParams := *&events.MockEventParameters{ + FromUserID: fromUser, + ToUserID: toUser, + Transport: "fake_transport", + Trigger: "shoutout-create", + SubscriptionStatus: "enabled", + } + endParams := *&events.MockEventParameters{ + FromUserID: fromUser, + ToUserID: toUser, + Transport: "fake_transport", + Trigger: "shoutout-received", + SubscriptionStatus: "enabled", + } + + r1, err1 := Event{}.GenerateEvent(beginParams) + r2, err2 := Event{}.GenerateEvent(endParams) + a.Nil(err1) + a.Nil(err2) + a.Empty(r1) + a.Empty(r2) +} +func TestValidTrigger(t *testing.T) { + a := test_setup.SetupTestEnv(t) + + r := Event{}.ValidTrigger("shoutout-create") + a.Equal(true, r) + + r = Event{}.ValidTrigger("shoutout-received") + a.Equal(true, r) + + r = Event{}.ValidTrigger("notshoutout") + a.Equal(false, r) +} + +func TestValidTransport(t *testing.T) { + a := test_setup.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 := test_setup.SetupTestEnv(t) + + r := Event{}.GetTopic(models.TransportEventSub, "shoutout-create") + a.NotNil(r) + + r = Event{}.GetTopic(models.TransportEventSub, "shoutout-receieve") + a.NotNil(r) +} diff --git a/internal/events/types/types.go b/internal/events/types/types.go index 0d7642b8..d7d468c9 100644 --- a/internal/events/types/types.go +++ b/internal/events/types/types.go @@ -23,6 +23,7 @@ import ( "github.com/twitchdev/twitch-cli/internal/events/types/prediction" "github.com/twitchdev/twitch-cli/internal/events/types/raid" "github.com/twitchdev/twitch-cli/internal/events/types/shield_mode" + "github.com/twitchdev/twitch-cli/internal/events/types/shoutout" "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" @@ -51,6 +52,7 @@ func All() []events.MockEvent { prediction.Event{}, raid.Event{}, shield_mode.Event{}, + shoutout.Event{}, stream_change.Event{}, streamup.Event{}, streamdown.Event{}, diff --git a/internal/models/shoutout.go b/internal/models/shoutout.go new file mode 100644 index 00000000..c63baec5 --- /dev/null +++ b/internal/models/shoutout.go @@ -0,0 +1,44 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package models + +// channel.shoutout.create + +type ShoutoutCreateEventSubResponse struct { + Subscription EventsubSubscription `json:"subscription"` + Event ShoutoutCreateEventSubEvent `json:"event"` +} + +type ShoutoutCreateEventSubEvent struct { + BroadcasterUserID string `json:"broadcaster_user_id"` + BroadcasterUserName string `json:"broadcaster_user_name"` + BroadcasterUserLogin string `json:"broadcaster_user_login"` + ToBroadcasterUserID string `json:"to_broadcaster_user_id"` + ToBroadcasterUserName string `json:"to_broadcaster_user_name"` + ToBroadcasterUserLogin string `json:"to_broadcaster_user_login"` + ModeratorUserID string `json:"moderator_user_id"` + ModeratorUserName string `json:"moderator_user_name"` + ModeratorUserLogin string `json:"moderator_user_login"` + ViewerCount int `json:"viewer_count"` + StartedAt string `json:"started_at"` + CooldownEndsAt string `json:"cooldown_ends_at"` + TargetCooldownEndsAt string `json:"target_cooldown_ends_at"` +} + +// channel.shoutout.receive + +type ShoutoutReceivedEventSubResponse struct { + Subscription EventsubSubscription `json:"subscription"` + Event ShoutoutReceivedEventSubEvent `json:"event"` +} + +type ShoutoutReceivedEventSubEvent struct { + BroadcasterUserID string `json:"broadcaster_user_id"` + BroadcasterUserName string `json:"broadcaster_user_name"` + BroadcasterUserLogin string `json:"broadcaster_user_login"` + FromBroadcasterUserID string `json:"from_broadcaster_user_id"` + FromBroadcasterUserName string `json:"from_broadcaster_user_name"` + FromBroadcasterUserLogin string `json:"from_broadcaster_user_login"` + ViewerCount int `json:"viewer_count"` + StartedAt string `json:"started_at"` +}