diff --git a/docs/event.md b/docs/event.md index 2881622f..40b627b9 100644 --- a/docs/event.md +++ b/docs/event.md @@ -16,20 +16,20 @@ Used to either create or send mock events for use with local webhooks testing. **Args** -| Argument | Description | -|---------------------|--------------------------------------------------------------------| -| `subscribe` | A standard subscription event. Triggers a basic tier 1 sub. | -| `unsubscribe` | A standard unsubscribe event. Triggers a basic tier 1 sub. | -| `gift` | A gifted subscription event. Triggers a basic tier 1 sub. | -| `cheer` | Only usable with the `eventsub` transport, shows Cheers from chat. | -| `transaction` | Bits in Extensions transactions events. | -| `add-reward` | Channel Points EventSub event for a Custom Reward being added. | -| `update-reward` | Channel Points EventSub event for a Custom Reward being updated. | -| `remove-reward` | Channel Points EventSub event for a Custom Reward being removed. | -| `add-redemption` | Channel Points EventSub event for a redemption being performed. | -| `update-redemption` | Channel Points EventSub event for a redemption being updated. | - - +| Argument | Description | +|---------------------|------------------------------------------------------------------------------------------------------------| +| `subscribe` | A standard subscription event. Triggers a basic tier 1 sub. | +| `unsubscribe` | A standard unsubscribe event. Triggers a basic tier 1 sub. | +| `gift` | A gifted subscription event. Triggers a basic tier 1 sub. | +| `cheer` | Only usable with the `eventsub` transport, shows Cheers from chat. | +| `transaction` | Bits in Extensions transactions events. | +| `add-reward` | Channel Points EventSub event for a Custom Reward being added. | +| `update-reward` | Channel Points EventSub event for a Custom Reward being updated. | +| `remove-reward` | Channel Points EventSub event for a Custom Reward being removed. | +| `add-redemption` | Channel Points EventSub event for a redemption being performed. | +| `update-redemption` | Channel Points EventSub event for a redemption being updated. | +| `raid` | Channel Raid event with a random viewer count. | +| `revoke` | User authorization revoke event. Uses local Client as set in `twitch configure` or generates one randomly. | **Flags** @@ -95,18 +95,20 @@ Allows you to test if your webserver responds to subscription requests properly. **Args** -| Argument | Description | -|---------------------|--------------------------------------------------------------------| -| `subscribe` | A standard subscription event. Triggers a basic tier 1 sub. | -| `unsubscribe` | A standard unsubscribe event. Triggers a basic tier 1 sub. | -| `gift` | A gifted subscription event. Triggers a basic tier 1 sub. | -| `cheer` | Only usable with the `eventsub` transport, shows Cheers from chat. | -| `transaction` | Bits in Extensions transactions events. | -| `add-reward` | Channel Points EventSub event for a Custom Reward being added. | -| `update-reward` | Channel Points EventSub event for a Custom Reward being updated. | -| `remove-reward` | Channel Points EventSub event for a Custom Reward being removed. | -| `add-redemption` | Channel Points EventSub event for a redemption being performed. | -| `update-redemption` | Channel Points EventSub event for a redemption being updated. | +| Argument | Description | +|---------------------|------------------------------------------------------------------------------------------------------------| +| `subscribe` | A standard subscription event. Triggers a basic tier 1 sub. | +| `unsubscribe` | A standard unsubscribe event. Triggers a basic tier 1 sub. | +| `gift` | A gifted subscription event. Triggers a basic tier 1 sub. | +| `cheer` | Only usable with the `eventsub` transport, shows Cheers from chat. | +| `transaction` | Bits in Extensions transactions events. | +| `add-reward` | Channel Points EventSub event for a Custom Reward being added. | +| `update-reward` | Channel Points EventSub event for a Custom Reward being updated. | +| `remove-reward` | Channel Points EventSub event for a Custom Reward being removed. | +| `add-redemption` | Channel Points EventSub event for a redemption being performed. | +| `update-redemption` | Channel Points EventSub event for a redemption being updated. | +| `raid` | Channel Raid event with a random viewer count. | +| `revoke` | User authorization revoke event. Uses local Client as set in `twitch configure` or generates one randomly. | **Flags** diff --git a/internal/events/types/_template/_event_name.go b/internal/events/types/_template/_event_name.go index a571a330..42eda791 100644 --- a/internal/events/types/_template/_event_name.go +++ b/internal/events/types/_template/_event_name.go @@ -27,7 +27,7 @@ var triggerMapping = map[string]map[string]string{ type Event struct{} -func (e Event) GenerateEvent(params events.MockEventParameters) (events.MockEventResponse, err) { +func (e Event) GenerateEvent(params events.MockEventParameters) (events.MockEventResponse, error) { var event []byte var err error @@ -70,3 +70,6 @@ func (e Event) ValidTrigger(t string) bool { } return false } +func (e Event) GetTopic(transport string, trigger string) string { + return triggerMapping[transport][trigger] +} diff --git a/internal/events/types/_template/_event_name_test.go b/internal/events/types/_template/_event_name_test.go index 4cb0811f..f1a8a1d3 100644 --- a/internal/events/types/_template/_event_name_test.go +++ b/internal/events/types/_template/_event_name_test.go @@ -86,3 +86,9 @@ func TestValidTransport(t *testing.T) { r = Event{}.ValidTransport("noteventsub") a.Equal(false, r) } +func TestGetTopic(t *testing.T) { + a := util.SetupTestEnv(t) + + r := Event{}.GetTopic(models.TransportEventSub, "trigger_keyword") + a.NotNil(r) +} diff --git a/internal/events/types/authorization_revoke/authorization_revoke.go b/internal/events/types/authorization_revoke/authorization_revoke.go new file mode 100644 index 00000000..a96b17f1 --- /dev/null +++ b/internal/events/types/authorization_revoke/authorization_revoke.go @@ -0,0 +1,96 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package authorization_revoke + +import ( + "encoding/json" + "errors" + "time" + + "github.com/spf13/viper" + "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.TransportWebSub: false, + models.TransportEventSub: true, +} + +var triggerSupported = []string{"revoke"} + +var triggerMapping = map[string]map[string]string{ + models.TransportEventSub: { + "trigger_keyword": "revoke", + }, +} + +type Event struct{} + +func (e Event) GenerateEvent(params events.MockEventParameters) (events.MockEventResponse, error) { + var event []byte + var err error + clientID := viper.GetString("ClientID") + + // if not configured, generate a random one + if clientID == "" { + clientID = util.RandomClientID() + } + switch params.Transport { + case models.TransportEventSub: + body := &models.AuthorizationRevokeEventSubResponse{ + Subscription: models.EventsubSubscription{ + ID: params.ID, + Status: "enabled", + Type: triggerMapping[params.Transport][params.Trigger], + Version: "1", + Condition: models.EventsubCondition{ + ClientID: clientID, + }, + Transport: models.EventsubTransport{ + Method: "webhook", + Callback: "null", + }, + CreatedAt: util.GetTimestamp().Format(time.RFC3339Nano), + }, + Event: models.AuthorizationRevokeEvent{ + ClientID: clientID, + UserID: params.FromUserID, + UserLogin: params.FromUserName, + UserName: params.FromUserName, + }, + } + event, err = json.Marshal(body) + if err != nil { + return events.MockEventResponse{}, err + } + case models.TransportWebSub: + return events.MockEventResponse{}, errors.New("Websub is unsupported for authorization revoke events") + 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/authorization_revoke/authorization_revoke_test.go b/internal/events/types/authorization_revoke/authorization_revoke_test.go new file mode 100644 index 00000000..7f3eb282 --- /dev/null +++ b/internal/events/types/authorization_revoke/authorization_revoke_test.go @@ -0,0 +1,93 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package authorization_revoke + +import ( + "encoding/json" + "testing" + + "github.com/spf13/viper" + "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) + + viper.Set("ClientID", "1234") + params := *&events.MockEventParameters{ + FromUserID: fromUser, + ToUserID: toUser, + Transport: models.TransportEventSub, + Trigger: "subscribe", + } + + r, err := Event{}.GenerateEvent(params) + a.Nil(err) + + var body models.AuthorizationRevokeEventSubResponse // replace with actual value + err = json.Unmarshal(r.JSON, &body) + a.Nil(err) + + a.NotEmpty(body.Event.ClientID) + a.Equal(body.Event.ClientID, body.Subscription.Condition.ClientID) + a.Equal("1234", body.Event.ClientID) +} +func TestWebSub(t *testing.T) { + a := util.SetupTestEnv(t) + + params := *&events.MockEventParameters{ + FromUserID: fromUser, + ToUserID: toUser, + Transport: models.TransportWebSub, + Trigger: "revoke", + } + + _, err := Event{}.GenerateEvent(params) + a.NotNil(err) + + // write tests here for websub +} +func TestFakeTransport(t *testing.T) { + a := util.SetupTestEnv(t) + + params := *&events.MockEventParameters{ + FromUserID: fromUser, + ToUserID: toUser, + Transport: "fake_transport", + Trigger: "unsubscribe", + } + + r, err := Event{}.GenerateEvent(params) + a.Nil(err) + a.Empty(r) +} +func TestValidTrigger(t *testing.T) { + a := util.SetupTestEnv(t) + + r := Event{}.ValidTrigger("revoke") + a.Equal(true, r) + + r = Event{}.ValidTrigger("fake_revoke") + 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, "revoke") + a.NotNil(r) +} diff --git a/internal/events/types/raid/raid.go b/internal/events/types/raid/raid.go new file mode 100644 index 00000000..1a1a4be3 --- /dev/null +++ b/internal/events/types/raid/raid.go @@ -0,0 +1,93 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package raid + +import ( + "encoding/json" + "errors" + "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.TransportWebSub: false, + models.TransportEventSub: true, +} + +var triggerSupported = []string{"raid"} + +var triggerMapping = map[string]map[string]string{ + models.TransportEventSub: { + "raid": "channel.raid", + }, +} + +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: + body := &models.RaidEventSubResponse{ + Subscription: models.EventsubSubscription{ + ID: params.ID, + Status: "enabled", + Type: triggerMapping[params.Transport][params.Trigger], + Version: "beta", + Condition: models.EventsubCondition{ + ToBroadcasterUserID: params.ToUserID, + }, + Transport: models.EventsubTransport{ + Method: "webhook", + Callback: "null", + }, + CreatedAt: util.GetTimestamp().Format(time.RFC3339Nano), + }, + Event: models.RaidEvent{ + ToBroadcasterUserID: params.ToUserID, + ToBroadcasterUserLogin: params.ToUserName, + ToBroadcasterUserName: params.ToUserName, + FromBroadcasterUserID: params.FromUserID, + FromBroadcasterUserLogin: params.FromUserName, + FromBroadcasterUserName: params.FromUserName, + Viewers: util.RandomViewerCount(), + }, + } + event, err = json.Marshal(body) + if err != nil { + return events.MockEventResponse{}, err + } + case models.TransportWebSub: + return events.MockEventResponse{}, errors.New("Raids are unsupported for websub") + 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/raid/raid_test.go b/internal/events/types/raid/raid_test.go new file mode 100644 index 00000000..e5b92365 --- /dev/null +++ b/internal/events/types/raid/raid_test.go @@ -0,0 +1,90 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package raid + +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: "raid", + } + + r, err := Event{}.GenerateEvent(params) + a.Nil(err) + + var body models.SubEventSubResponse // replace with actual value + err = json.Unmarshal(r.JSON, &body) + a.Nil(err) + + // write actual tests here (making sure you set appropriate values and the like) for eventsub +} + +func TestWebSub(t *testing.T) { + a := util.SetupTestEnv(t) + + params := *&events.MockEventParameters{ + FromUserID: fromUser, + ToUserID: toUser, + Transport: models.TransportWebSub, + Trigger: "raid", + } + + _, err := Event{}.GenerateEvent(params) + a.NotNil(err) + + // write tests here for websub +} +func TestFakeTransport(t *testing.T) { + a := util.SetupTestEnv(t) + + params := *&events.MockEventParameters{ + FromUserID: fromUser, + ToUserID: toUser, + Transport: "fake_transport", + Trigger: "raid", + } + + r, err := Event{}.GenerateEvent(params) + a.Nil(err) + a.Empty(r) +} +func TestValidTrigger(t *testing.T) { + a := util.SetupTestEnv(t) + + r := Event{}.ValidTrigger("raid") + a.Equal(true, r) + + r = Event{}.ValidTrigger("not_raid") + 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, "trigger_keyword") + a.NotNil(r) +} diff --git a/internal/events/types/types.go b/internal/events/types/types.go index bb3f97b6..dc71c38e 100644 --- a/internal/events/types/types.go +++ b/internal/events/types/types.go @@ -6,21 +6,25 @@ import ( "errors" "github.com/twitchdev/twitch-cli/internal/events" + "github.com/twitchdev/twitch-cli/internal/events/types/authorization_revoke" "github.com/twitchdev/twitch-cli/internal/events/types/channel_points_redemption" "github.com/twitchdev/twitch-cli/internal/events/types/channel_points_reward" "github.com/twitchdev/twitch-cli/internal/events/types/cheer" "github.com/twitchdev/twitch-cli/internal/events/types/extension_transaction" "github.com/twitchdev/twitch-cli/internal/events/types/follow" + "github.com/twitchdev/twitch-cli/internal/events/types/raid" "github.com/twitchdev/twitch-cli/internal/events/types/subscribe" ) func All() []events.MockEvent { return []events.MockEvent{ + authorization_revoke.Event{}, channel_points_redemption.Event{}, channel_points_reward.Event{}, cheer.Event{}, extension_transaction.Event{}, follow.Event{}, + raid.Event{}, subscribe.Event{}, } } diff --git a/internal/models/authorization_revoke.go b/internal/models/authorization_revoke.go new file mode 100644 index 00000000..5a398f2f --- /dev/null +++ b/internal/models/authorization_revoke.go @@ -0,0 +1,15 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package models + +type AuthorizationRevokeEvent struct { + UserID string `json:"user_id"` + UserLogin string `json:"user_login"` + UserName string `json:"user_name"` + ClientID string `json:"client_id"` +} + +type AuthorizationRevokeEventSubResponse struct { + Subscription EventsubSubscription `json:"subscription"` + Event AuthorizationRevokeEvent `json:"event"` +} diff --git a/internal/models/eventsub.go b/internal/models/eventsub.go index 1654e2a7..ba378760 100644 --- a/internal/models/eventsub.go +++ b/internal/models/eventsub.go @@ -18,7 +18,10 @@ type EventsubTransport struct { } type EventsubCondition struct { - BroadcasterUserID string `json:"broadcaster_user_id"` + BroadcasterUserID string `json:"broadcaster_user_id,omitempty"` + ToBroadcasterUserID string `json:"to_broadcaster_user_id,omitempty"` + FromBroadcasterUserID string `json:"from_broadcaster_user_id,omitempty"` + ClientID string `json:"client_id,omitempty"` } type EventsubResponse struct { diff --git a/internal/models/raids.go b/internal/models/raids.go new file mode 100644 index 00000000..770e8fef --- /dev/null +++ b/internal/models/raids.go @@ -0,0 +1,18 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package models + +type RaidEvent struct { + ToBroadcasterUserID string `json:"to_broadcaster_user_id"` + ToBroadcasterUserLogin string `json:"to_broadcaster_user_login"` + ToBroadcasterUserName string `json:"to_broadcaster_user_name"` + FromBroadcasterUserID string `json:"from_broadcaster_user_id"` + FromBroadcasterUserLogin string `json:"from_broadcaster_user_login"` + FromBroadcasterUserName string `json:"from_broadcaster_user_name"` + Viewers int64 `json:"viewers"` +} + +type RaidEventSubResponse struct { + Subscription EventsubSubscription `json:"subscription"` + Event RaidEvent `json:"event"` +} diff --git a/internal/util/random.go b/internal/util/random.go index 58ac6165..6e7ceb15 100644 --- a/internal/util/random.go +++ b/internal/util/random.go @@ -30,3 +30,19 @@ func RandomGUID() string { return uuid } + +// RandomClientID generates a fake client ID of length 30 +func RandomClientID() string { + b := make([]byte, 30) + rand.Read(b) + return fmt.Sprintf("%x", b)[:30] +} + +// RandomViewerCount generates a fake viewercount between 0->10,000,000 +func RandomViewerCount() int64 { + viewer, err := rand.Int(rand.Reader, big.NewInt(1*10*100*100*100)) + if err != nil { + log.Fatal(err.Error()) + } + return viewer.Int64() +} diff --git a/internal/util/random_test.go b/internal/util/random_test.go index 8ad31192..8f095771 100644 --- a/internal/util/random_test.go +++ b/internal/util/random_test.go @@ -24,3 +24,16 @@ func TestRandomGUID(t *testing.T) { a.NotEqual(0, len(guid), "RandomGUID() returned string with a length of 0") a.Equal(true, r.MatchString(guid), "RandomGUID() returned a string with value %v, which does not meet the GUID pattern", guid) } + +func TestRandomClientID(t *testing.T) { + a := assert.New(t) + clientID := RandomClientID() + + a.Equal(30, len(clientID)) +} +func TestRandomViewerCount(t *testing.T) { + a := assert.New(t) + viewers := RandomViewerCount() + + a.NotEmpty(viewers) +}