Skip to content

Commit 6abe278

Browse files
committed
Added channel.follow v2, and included backbone for future version changes via --version flag
1 parent 1f3fdda commit 6abe278

File tree

10 files changed

+258
-11
lines changed

10 files changed

+258
-11
lines changed

cmd/events.go

+4
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ var (
4343
charityCurrentValue int
4444
charityTargetValue int
4545
clientId string
46+
version string
4647
websocketClient string
4748
)
4849

@@ -152,6 +153,7 @@ func init() {
152153
triggerCmd.Flags().IntVar(&charityCurrentValue, "charity-current-value", 0, "Only used for \"charity-*\" events. Manually set the current dollar value for charity events.")
153154
triggerCmd.Flags().IntVar(&charityTargetValue, "charity-target-value", 1500000, "Only used for \"charity-*\" events. Manually set the target dollar value for charity events.")
154155
triggerCmd.Flags().StringVar(&clientId, "client-id", "", "Manually set the Client ID used in revoke, grant, and bits transaction events.")
156+
triggerCmd.Flags().StringVarP(&version, "version", "v", "", "Chooses the EventSub version used for a specific event. Not required for most events.")
155157
triggerCmd.Flags().StringVar(&websocketClient, "session", "", "Defines a specific websocket client/session to forward an event to. Used only with \"websocket\" transport.")
156158

157159
// retrigger flags
@@ -166,6 +168,7 @@ func init() {
166168
verifyCmd.Flags().StringVarP(&secret, "secret", "s", "", "Webhook secret. If defined, signs all forwarded events with the SHA256 HMAC and must be 10-100 characters in length.")
167169
verifyCmd.Flags().StringVar(&timestamp, "timestamp", "", "Sets the timestamp to be used in payloads and headers. Must be in RFC3339Nano format.")
168170
verifyCmd.Flags().StringVarP(&eventID, "subscription-id", "u", "", "Manually set the subscription/event ID of the event itself.") // TODO: This description will need to change with https://github.com/twitchdev/twitch-cli/issues/184
171+
verifyCmd.Flags().StringVarP(&version, "version", "v", "", "Chooses the EventSub version used for a specific event. Not required for most events.")
169172
verifyCmd.MarkFlagRequired("forward-address")
170173

171174
// websocket flags
@@ -230,6 +233,7 @@ func triggerCmdRun(cmd *cobra.Command, args []string) {
230233
CharityCurrentValue: charityCurrentValue,
231234
CharityTargetValue: charityTargetValue,
232235
ClientID: clientId,
236+
Version: version,
233237
WebSocketClient: websocketClient,
234238
})
235239

internal/events/trigger/retrigger_event.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
package trigger
44

55
import (
6+
"encoding/json"
67
"fmt"
78

89
"github.com/twitchdev/twitch-cli/internal/database"
910
"github.com/twitchdev/twitch-cli/internal/events/types"
11+
"github.com/twitchdev/twitch-cli/internal/models"
1012
)
1113

1214
func RefireEvent(id string, p TriggerParameters) (string, error) {
@@ -21,7 +23,13 @@ func RefireEvent(id string, p TriggerParameters) (string, error) {
2123

2224
p.Transport = res.Transport
2325

24-
e, err := types.GetByTriggerAndTransport(res.Event, p.Transport)
26+
var previousEventObj models.EventsubSubscription
27+
err = json.Unmarshal([]byte(res.JSON), &previousEventObj)
28+
if err != nil {
29+
return "", fmt.Errorf("Unable to parse previous event's JSON from database: %v", err.Error())
30+
}
31+
32+
e, err := types.GetByTriggerAndTransportAndVersion(res.Event, p.Transport, previousEventObj.Version)
2533
if err != nil {
2634
return "", err
2735
}

internal/events/trigger/trigger_event.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ type TriggerParameters struct {
4646
CharityCurrentValue int
4747
CharityTargetValue int
4848
ClientID string
49+
Version string
4950
WebSocketClient string
5051
}
5152

@@ -135,7 +136,7 @@ https://dev.twitch.tv/docs/eventsub/handling-webhook-events#processing-an-event`
135136
ClientID: p.ClientID,
136137
}
137138

138-
e, err := types.GetByTriggerAndTransport(p.Event, p.Transport)
139+
e, err := types.GetByTriggerAndTransportAndVersion(p.Event, p.Transport, p.Version)
139140
if err != nil {
140141
return "", err
141142
}

internal/events/types/follow/follow_event.go renamed to internal/events/types/follow_v1/follow_event.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3-
package follow
3+
package follow_v1
44

55
import (
66
"encoding/json"

internal/events/types/follow/follow_event_test.go renamed to internal/events/types/follow_v1/follow_event_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3-
package follow
3+
package follow_v1
44

55
import (
66
"encoding/json"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package follow_v2
4+
5+
import (
6+
"encoding/json"
7+
"strings"
8+
9+
"github.com/twitchdev/twitch-cli/internal/events"
10+
"github.com/twitchdev/twitch-cli/internal/models"
11+
)
12+
13+
var transportsSupported = map[string]bool{
14+
models.TransportWebhook: true,
15+
models.TransportWebSocket: true,
16+
}
17+
var triggers = []string{"follow"}
18+
19+
var triggerMapping = map[string]map[string]string{
20+
models.TransportWebhook: {
21+
"follow": "channel.follow",
22+
},
23+
models.TransportWebSocket: {
24+
"follow": "channel.follow",
25+
},
26+
}
27+
28+
type Event struct{}
29+
30+
func (e Event) GenerateEvent(params events.MockEventParameters) (events.MockEventResponse, error) {
31+
var event []byte
32+
var err error
33+
34+
switch params.Transport {
35+
case models.TransportWebhook, models.TransportWebSocket:
36+
body := models.EventsubResponse{
37+
Subscription: models.EventsubSubscription{
38+
ID: params.ID,
39+
Status: params.SubscriptionStatus,
40+
Type: triggerMapping[params.Transport][params.Trigger],
41+
Version: e.SubscriptionVersion(),
42+
Condition: models.EventsubCondition{
43+
BroadcasterUserID: params.ToUserID,
44+
ModeratorUserID: params.FromUserID,
45+
},
46+
Transport: models.EventsubTransport{
47+
Method: "webhook",
48+
Callback: "null",
49+
},
50+
Cost: 0,
51+
CreatedAt: params.Timestamp,
52+
},
53+
Event: models.FollowEventSubEvent{
54+
UserID: params.FromUserID,
55+
UserLogin: params.FromUserName,
56+
UserName: params.FromUserName,
57+
BroadcasterUserID: params.ToUserID,
58+
BroadcasterUserLogin: params.ToUserID,
59+
BroadcasterUserName: params.ToUserName,
60+
FollowedAt: params.Timestamp,
61+
},
62+
}
63+
64+
event, err = json.Marshal(body)
65+
if err != nil {
66+
return events.MockEventResponse{}, err
67+
}
68+
69+
// Delete event info if Subscription.Status is not set to "enabled"
70+
if !strings.EqualFold(params.SubscriptionStatus, "enabled") {
71+
var i interface{}
72+
if err := json.Unmarshal([]byte(event), &i); err != nil {
73+
return events.MockEventResponse{}, err
74+
}
75+
if m, ok := i.(map[string]interface{}); ok {
76+
delete(m, "event") // Matches JSON key defined in body variable above
77+
}
78+
79+
event, err = json.Marshal(i)
80+
if err != nil {
81+
return events.MockEventResponse{}, err
82+
}
83+
}
84+
default:
85+
return events.MockEventResponse{}, nil
86+
}
87+
88+
return events.MockEventResponse{
89+
ID: params.ID,
90+
JSON: event,
91+
FromUser: params.FromUserID,
92+
ToUser: params.ToUserID,
93+
}, nil
94+
}
95+
96+
func (e Event) ValidTransport(transport string) bool {
97+
return transportsSupported[transport]
98+
}
99+
100+
func (e Event) ValidTrigger(trigger string) bool {
101+
for _, t := range triggers {
102+
if t == trigger {
103+
return true
104+
}
105+
}
106+
return false
107+
}
108+
func (e Event) GetTopic(transport string, trigger string) string {
109+
return triggerMapping[transport][trigger]
110+
}
111+
func (e Event) GetAllTopicsByTransport(transport string) []string {
112+
allTopics := []string{}
113+
for _, topic := range triggerMapping[transport] {
114+
allTopics = append(allTopics, topic)
115+
}
116+
return allTopics
117+
}
118+
func (e Event) GetEventSubAlias(t string) string {
119+
// check for aliases
120+
for trigger, topic := range triggerMapping[models.TransportWebhook] {
121+
if topic == t {
122+
return trigger
123+
}
124+
}
125+
return ""
126+
}
127+
128+
func (e Event) SubscriptionVersion() string {
129+
return "2"
130+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package follow_v2
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+
params := *&events.MockEventParameters{
21+
FromUserID: fromUser,
22+
ToUserID: toUser,
23+
Transport: models.TransportWebhook,
24+
Trigger: "subscribe",
25+
SubscriptionStatus: "enabled",
26+
}
27+
28+
r, err := Event{}.GenerateEvent(params)
29+
a.Nil(err)
30+
31+
var body models.SubEventSubResponse
32+
err = json.Unmarshal(r.JSON, &body)
33+
a.Nil(err)
34+
35+
a.Equal(toUser, body.Event.BroadcasterUserID, "Expected to user %v, got %v", toUser, body.Event.BroadcasterUserID)
36+
a.Equal(fromUser, body.Event.UserID, "Expected from user %v, got %v", r.ToUser, body.Event.UserID)
37+
}
38+
39+
func TestFakeTransport(t *testing.T) {
40+
a := test_setup.SetupTestEnv(t)
41+
42+
params := *&events.MockEventParameters{
43+
FromUserID: fromUser,
44+
ToUserID: toUser,
45+
Transport: "fake_transport",
46+
Trigger: triggers[0],
47+
SubscriptionStatus: "enabled",
48+
}
49+
50+
r, err := Event{}.GenerateEvent(params)
51+
a.Nil(err)
52+
a.Empty(r)
53+
}
54+
func TestValidTrigger(t *testing.T) {
55+
a := test_setup.SetupTestEnv(t)
56+
57+
r := Event{}.ValidTrigger("notreal")
58+
a.Equal(false, r)
59+
60+
r = Event{}.ValidTrigger("follow")
61+
a.Equal(true, r)
62+
}
63+
64+
func TestValidTransport(t *testing.T) {
65+
a := test_setup.SetupTestEnv(t)
66+
67+
r := Event{}.ValidTransport(models.TransportWebhook)
68+
a.Equal(true, r)
69+
70+
r = Event{}.ValidTransport("noteventsub")
71+
a.Equal(false, r)
72+
}
73+
func TestGetTopic(t *testing.T) {
74+
a := test_setup.SetupTestEnv(t)
75+
76+
r := Event{}.GetTopic(models.TransportWebhook, "follow")
77+
a.NotNil(r)
78+
}

internal/events/types/types.go

+30-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package types
44

55
import (
66
"errors"
7+
"fmt"
78
"sort"
89
"strings"
910

@@ -17,7 +18,8 @@ import (
1718
"github.com/twitchdev/twitch-cli/internal/events/types/cheer"
1819
"github.com/twitchdev/twitch-cli/internal/events/types/drop"
1920
"github.com/twitchdev/twitch-cli/internal/events/types/extension_transaction"
20-
"github.com/twitchdev/twitch-cli/internal/events/types/follow"
21+
"github.com/twitchdev/twitch-cli/internal/events/types/follow_v1"
22+
"github.com/twitchdev/twitch-cli/internal/events/types/follow_v2"
2123
"github.com/twitchdev/twitch-cli/internal/events/types/gift"
2224
"github.com/twitchdev/twitch-cli/internal/events/types/goal"
2325
"github.com/twitchdev/twitch-cli/internal/events/types/hype_train"
@@ -47,7 +49,8 @@ func AllEvents() []events.MockEvent {
4749
cheer.Event{},
4850
drop.Event{},
4951
extension_transaction.Event{},
50-
follow.Event{},
52+
follow_v1.Event{},
53+
follow_v2.Event{},
5154
gift.Event{},
5255
goal.Event{},
5356
hype_train.Event{},
@@ -103,7 +106,10 @@ func WebSocketCommandTopics() []string {
103106
return allEvents
104107
}
105108

106-
func GetByTriggerAndTransport(trigger string, transport string) (events.MockEvent, error) {
109+
func GetByTriggerAndTransportAndVersion(trigger string, transport string, version string) (events.MockEvent, error) {
110+
validEventBadVersions := []string{}
111+
var latestEventSeen events.MockEvent
112+
107113
for _, e := range AllEvents() {
108114
if transport == models.TransportWebhook || transport == models.TransportWebSocket {
109115
newTrigger := e.GetEventSubAlias(trigger)
@@ -112,11 +118,30 @@ func GetByTriggerAndTransport(trigger string, transport string) (events.MockEven
112118
}
113119
}
114120
if e.ValidTrigger(trigger) == true && e.ValidTransport(transport) == true {
115-
return e, nil
121+
if e.SubscriptionVersion() == version {
122+
return e, nil
123+
} else {
124+
validEventBadVersions = append(validEventBadVersions, e.SubscriptionVersion())
125+
latestEventSeen = e
126+
}
127+
}
128+
}
129+
130+
// When no version is given, and there's only one version available, use the default version.
131+
if version == "" && len(validEventBadVersions) == 1 {
132+
return latestEventSeen, nil
133+
}
134+
135+
// Error for events with non-existent verison used
136+
if len(validEventBadVersions) != 0 {
137+
errStr := fmt.Sprintf("Invalid version given. Valid version(s): %v", strings.Join(validEventBadVersions, ", "))
138+
if version == "" {
139+
errStr += "\nUse --verison to specify"
116140
}
141+
return nil, errors.New(errStr)
117142
}
118143

119-
// Different error for websocket transport
144+
// Error for websocket transport
120145
if strings.EqualFold(transport, "websocket") {
121146
return nil, errors.New("Invalid event, or this event is not available via WebSockets.")
122147
}

internal/events/verify/subscription_verify.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type VerifyParameters struct {
2525
ForwardAddress string
2626
Secret string
2727
EventID string
28+
Version string
2829
}
2930

3031
type VerifyResponse struct {
@@ -38,7 +39,7 @@ func VerifyWebhookSubscription(p VerifyParameters) (VerifyResponse, error) {
3839

3940
challenge := util.RandomGUID()
4041

41-
event, err := types.GetByTriggerAndTransport(p.Event, p.Transport)
42+
event, err := types.GetByTriggerAndTransportAndVersion(p.Event, p.Transport, p.Version)
4243
if err != nil {
4344
return VerifyResponse{}, err
4445
}

0 commit comments

Comments
 (0)