Skip to content

Commit 78ce45b

Browse files
authored
Merge pull request #209 from twitchdev/eventsub-shoutout-beta
Support for new Shoutout EventSub topics
2 parents 05945ee + 7db9615 commit 78ce45b

File tree

5 files changed

+324
-0
lines changed

5 files changed

+324
-0
lines changed

internal/events/models.go

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ var triggerSupported = map[string]bool{
3535
"revoke": true,
3636
"shield-mode-begin": true,
3737
"shield-mode-end": true,
38+
"shoutout-create": true,
39+
"shoutout-received": true,
3840
"stream-change": true,
3941
"streamdown": true,
4042
"streamup": true,
+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package shoutout
4+
5+
import (
6+
"encoding/json"
7+
"strings"
8+
"time"
9+
10+
"github.com/twitchdev/twitch-cli/internal/events"
11+
"github.com/twitchdev/twitch-cli/internal/models"
12+
"github.com/twitchdev/twitch-cli/internal/util"
13+
)
14+
15+
var transportsSupported = map[string]bool{
16+
models.TransportEventSub: true,
17+
}
18+
var triggers = []string{"shoutout-create", "shoutout-received"}
19+
20+
var triggerMapping = map[string]map[string]string{
21+
models.TransportEventSub: {
22+
"shoutout-create": "channel.shoutout.create",
23+
"shoutout-received": "channel.shoutout.receive",
24+
},
25+
}
26+
27+
type Event struct{}
28+
29+
func (e Event) GenerateEvent(params events.MockEventParameters) (events.MockEventResponse, error) {
30+
var event []byte
31+
var err error
32+
33+
switch params.Transport {
34+
case models.TransportEventSub:
35+
viewerCount := util.RandomInt(2000)
36+
startedAt := util.GetTimestamp()
37+
38+
moderatorUserID := "3502151007"
39+
40+
if params.Trigger == "shoutout-create" {
41+
body := models.ShoutoutCreateEventSubResponse{
42+
Subscription: models.EventsubSubscription{
43+
ID: params.ID,
44+
Status: params.SubscriptionStatus,
45+
Type: triggerMapping[params.Transport][params.Trigger],
46+
Version: e.SubscriptionVersion(),
47+
Condition: models.EventsubCondition{
48+
BroadcasterUserID: params.FromUserID,
49+
ModeratorUserID: moderatorUserID,
50+
},
51+
Transport: models.EventsubTransport{
52+
Method: "webhook",
53+
Callback: "null",
54+
},
55+
Cost: 0,
56+
CreatedAt: params.Timestamp,
57+
},
58+
Event: models.ShoutoutCreateEventSubEvent{
59+
BroadcasterUserID: params.FromUserID,
60+
BroadcasterUserName: params.FromUserName,
61+
BroadcasterUserLogin: params.FromUserName,
62+
ToBroadcasterUserID: params.ToUserID,
63+
ToBroadcasterUserName: params.ToUserName,
64+
ToBroadcasterUserLogin: params.ToUserName,
65+
ModeratorUserID: moderatorUserID,
66+
ModeratorUserName: "TrustedUser123",
67+
ModeratorUserLogin: "trusteduser123",
68+
ViewerCount: int(viewerCount),
69+
StartedAt: startedAt.Format(time.RFC3339Nano),
70+
CooldownEndsAt: startedAt.Add(2 * time.Minute).Format(time.RFC3339Nano),
71+
TargetCooldownEndsAt: startedAt.Add(1 * time.Hour).Format(time.RFC3339Nano),
72+
},
73+
}
74+
75+
event, err = json.Marshal(body)
76+
if err != nil {
77+
return events.MockEventResponse{}, err
78+
}
79+
} else if params.Trigger == "shoutout-received" {
80+
body := models.ShoutoutReceivedEventSubResponse{
81+
Subscription: models.EventsubSubscription{
82+
ID: params.ID,
83+
Status: params.SubscriptionStatus,
84+
Type: triggerMapping[params.Transport][params.Trigger],
85+
Version: e.SubscriptionVersion(),
86+
Condition: models.EventsubCondition{
87+
BroadcasterUserID: params.ToUserID,
88+
ModeratorUserID: moderatorUserID,
89+
},
90+
Transport: models.EventsubTransport{
91+
Method: "webhook",
92+
Callback: "null",
93+
},
94+
Cost: 0,
95+
CreatedAt: params.Timestamp,
96+
},
97+
Event: models.ShoutoutReceivedEventSubEvent{
98+
BroadcasterUserID: params.ToUserID,
99+
BroadcasterUserName: params.ToUserName,
100+
BroadcasterUserLogin: params.ToUserName,
101+
FromBroadcasterUserID: params.FromUserID,
102+
FromBroadcasterUserName: params.FromUserName,
103+
FromBroadcasterUserLogin: params.FromUserName,
104+
ViewerCount: int(viewerCount),
105+
StartedAt: startedAt.Format(time.RFC3339Nano),
106+
},
107+
}
108+
109+
event, err = json.Marshal(body)
110+
if err != nil {
111+
return events.MockEventResponse{}, err
112+
}
113+
}
114+
115+
// Delete event info if Subscription.Status is not set to "enabled"
116+
if !strings.EqualFold(params.SubscriptionStatus, "enabled") {
117+
var i interface{}
118+
if err := json.Unmarshal([]byte(event), &i); err != nil {
119+
return events.MockEventResponse{}, err
120+
}
121+
if m, ok := i.(map[string]interface{}); ok {
122+
delete(m, "event") // Matches JSON key defined in body variable above
123+
}
124+
125+
event, err = json.Marshal(i)
126+
if err != nil {
127+
return events.MockEventResponse{}, err
128+
}
129+
}
130+
default:
131+
return events.MockEventResponse{}, nil
132+
}
133+
134+
return events.MockEventResponse{
135+
ID: params.ID,
136+
JSON: event,
137+
ToUser: params.ToUserID,
138+
FromUser: params.FromUserID,
139+
}, nil
140+
}
141+
142+
func (e Event) ValidTransport(transport string) bool {
143+
return transportsSupported[transport]
144+
}
145+
146+
func (e Event) ValidTrigger(trigger string) bool {
147+
for _, t := range triggers {
148+
if t == trigger {
149+
return true
150+
}
151+
}
152+
return false
153+
}
154+
func (e Event) GetTopic(transport string, trigger string) string {
155+
return triggerMapping[transport][trigger]
156+
}
157+
func (e Event) GetEventSubAlias(t string) string {
158+
// check for aliases
159+
for trigger, topic := range triggerMapping[models.TransportEventSub] {
160+
if topic == t {
161+
return trigger
162+
}
163+
}
164+
return ""
165+
}
166+
167+
func (e Event) SubscriptionVersion() string {
168+
return "beta"
169+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package shoutout
4+
5+
import (
6+
"encoding/json"
7+
"testing"
8+
9+
"github.com/twitchdev/twitch-cli/internal/events"
10+
"github.com/twitchdev/twitch-cli/internal/models"
11+
"github.com/twitchdev/twitch-cli/test_setup"
12+
)
13+
14+
var fromUser = "1234"
15+
var toUser = "4567"
16+
17+
func TestEventSub(t *testing.T) {
18+
a := test_setup.SetupTestEnv(t)
19+
20+
beginParams := *&events.MockEventParameters{
21+
FromUserID: fromUser,
22+
ToUserID: toUser,
23+
Transport: models.TransportEventSub,
24+
Trigger: "shoutout-create",
25+
SubscriptionStatus: "enabled",
26+
Cost: 0,
27+
}
28+
endParams := *&events.MockEventParameters{
29+
FromUserID: fromUser,
30+
ToUserID: toUser,
31+
Transport: models.TransportEventSub,
32+
Trigger: "shoutout-received",
33+
SubscriptionStatus: "enabled",
34+
Cost: 0,
35+
}
36+
37+
r1, err := Event{}.GenerateEvent(beginParams)
38+
a.Nil(err)
39+
40+
r2, err := Event{}.GenerateEvent(endParams)
41+
a.Nil(err)
42+
43+
var body1 models.ShoutoutCreateEventSubResponse
44+
err = json.Unmarshal(r1.JSON, &body1)
45+
a.Nil(err)
46+
47+
var body2 models.ShoutoutReceivedEventSubResponse
48+
err = json.Unmarshal(r2.JSON, &body2)
49+
a.Nil(err)
50+
}
51+
52+
func TestFakeTransport(t *testing.T) {
53+
a := test_setup.SetupTestEnv(t)
54+
55+
beginParams := *&events.MockEventParameters{
56+
FromUserID: fromUser,
57+
ToUserID: toUser,
58+
Transport: "fake_transport",
59+
Trigger: "shoutout-create",
60+
SubscriptionStatus: "enabled",
61+
}
62+
endParams := *&events.MockEventParameters{
63+
FromUserID: fromUser,
64+
ToUserID: toUser,
65+
Transport: "fake_transport",
66+
Trigger: "shoutout-received",
67+
SubscriptionStatus: "enabled",
68+
}
69+
70+
r1, err1 := Event{}.GenerateEvent(beginParams)
71+
r2, err2 := Event{}.GenerateEvent(endParams)
72+
a.Nil(err1)
73+
a.Nil(err2)
74+
a.Empty(r1)
75+
a.Empty(r2)
76+
}
77+
func TestValidTrigger(t *testing.T) {
78+
a := test_setup.SetupTestEnv(t)
79+
80+
r := Event{}.ValidTrigger("shoutout-create")
81+
a.Equal(true, r)
82+
83+
r = Event{}.ValidTrigger("shoutout-received")
84+
a.Equal(true, r)
85+
86+
r = Event{}.ValidTrigger("notshoutout")
87+
a.Equal(false, r)
88+
}
89+
90+
func TestValidTransport(t *testing.T) {
91+
a := test_setup.SetupTestEnv(t)
92+
93+
r := Event{}.ValidTransport(models.TransportEventSub)
94+
a.Equal(true, r)
95+
96+
r = Event{}.ValidTransport("noteventsub")
97+
a.Equal(false, r)
98+
}
99+
func TestGetTopic(t *testing.T) {
100+
a := test_setup.SetupTestEnv(t)
101+
102+
r := Event{}.GetTopic(models.TransportEventSub, "shoutout-create")
103+
a.NotNil(r)
104+
105+
r = Event{}.GetTopic(models.TransportEventSub, "shoutout-receieve")
106+
a.NotNil(r)
107+
}

internal/events/types/types.go

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/twitchdev/twitch-cli/internal/events/types/prediction"
2424
"github.com/twitchdev/twitch-cli/internal/events/types/raid"
2525
"github.com/twitchdev/twitch-cli/internal/events/types/shield_mode"
26+
"github.com/twitchdev/twitch-cli/internal/events/types/shoutout"
2627
"github.com/twitchdev/twitch-cli/internal/events/types/stream_change"
2728
"github.com/twitchdev/twitch-cli/internal/events/types/streamdown"
2829
"github.com/twitchdev/twitch-cli/internal/events/types/streamup"
@@ -51,6 +52,7 @@ func All() []events.MockEvent {
5152
prediction.Event{},
5253
raid.Event{},
5354
shield_mode.Event{},
55+
shoutout.Event{},
5456
stream_change.Event{},
5557
streamup.Event{},
5658
streamdown.Event{},

internal/models/shoutout.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package models
4+
5+
// channel.shoutout.create
6+
7+
type ShoutoutCreateEventSubResponse struct {
8+
Subscription EventsubSubscription `json:"subscription"`
9+
Event ShoutoutCreateEventSubEvent `json:"event"`
10+
}
11+
12+
type ShoutoutCreateEventSubEvent struct {
13+
BroadcasterUserID string `json:"broadcaster_user_id"`
14+
BroadcasterUserName string `json:"broadcaster_user_name"`
15+
BroadcasterUserLogin string `json:"broadcaster_user_login"`
16+
ToBroadcasterUserID string `json:"to_broadcaster_user_id"`
17+
ToBroadcasterUserName string `json:"to_broadcaster_user_name"`
18+
ToBroadcasterUserLogin string `json:"to_broadcaster_user_login"`
19+
ModeratorUserID string `json:"moderator_user_id"`
20+
ModeratorUserName string `json:"moderator_user_name"`
21+
ModeratorUserLogin string `json:"moderator_user_login"`
22+
ViewerCount int `json:"viewer_count"`
23+
StartedAt string `json:"started_at"`
24+
CooldownEndsAt string `json:"cooldown_ends_at"`
25+
TargetCooldownEndsAt string `json:"target_cooldown_ends_at"`
26+
}
27+
28+
// channel.shoutout.receive
29+
30+
type ShoutoutReceivedEventSubResponse struct {
31+
Subscription EventsubSubscription `json:"subscription"`
32+
Event ShoutoutReceivedEventSubEvent `json:"event"`
33+
}
34+
35+
type ShoutoutReceivedEventSubEvent struct {
36+
BroadcasterUserID string `json:"broadcaster_user_id"`
37+
BroadcasterUserName string `json:"broadcaster_user_name"`
38+
BroadcasterUserLogin string `json:"broadcaster_user_login"`
39+
FromBroadcasterUserID string `json:"from_broadcaster_user_id"`
40+
FromBroadcasterUserName string `json:"from_broadcaster_user_name"`
41+
FromBroadcasterUserLogin string `json:"from_broadcaster_user_login"`
42+
ViewerCount int `json:"viewer_count"`
43+
StartedAt string `json:"started_at"`
44+
}

0 commit comments

Comments
 (0)