Skip to content

Commit 6756211

Browse files
committed
refactoring 'event' subcommands
1 parent 83b47aa commit 6756211

File tree

8 files changed

+461
-394
lines changed

8 files changed

+461
-394
lines changed

cmd/events.go

Lines changed: 11 additions & 394 deletions
Large diffs are not rendered by default.

cmd/events/configure.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package events
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
configure_event "github.com/twitchdev/twitch-cli/internal/events/configure"
6+
)
7+
8+
func ConfigureCommand() (command *cobra.Command) {
9+
command = &cobra.Command{
10+
Use: "configure",
11+
Short: "Allows users to configure defaults for the twitch event subcommands.",
12+
RunE: configureEventRun,
13+
Example: `twitch event configure`,
14+
}
15+
16+
command.Flags().StringVarP(&forwardAddress, "forward-address", "F", "", "Forward address for mock event (webhook only).")
17+
command.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.")
18+
19+
return
20+
}
21+
22+
func configureEventRun(cmd *cobra.Command, args []string) error {
23+
return configure_event.ConfigureEvents(configure_event.EventConfigurationParams{
24+
ForwardAddress: forwardAddress,
25+
Secret: secret,
26+
})
27+
}

cmd/events/retrigger.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package events
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"github.com/spf13/cobra"
8+
configure_event "github.com/twitchdev/twitch-cli/internal/events/configure"
9+
"github.com/twitchdev/twitch-cli/internal/events/trigger"
10+
"github.com/twitchdev/twitch-cli/internal/util"
11+
)
12+
13+
func RetriggerCommand() (command *cobra.Command) {
14+
command = &cobra.Command{
15+
Use: "retrigger",
16+
Short: "Refires events based on the event ID. Can be forwarded to the local webserver for event testing.",
17+
RunE: retriggerCmdRun,
18+
Example: `twitch event retrigger subscribe`,
19+
}
20+
21+
command.Flags().StringVarP(&forwardAddress, "forward-address", "F", "", "Forward address for mock event (webhook only).")
22+
command.Flags().StringVarP(&eventID, "id", "i", "", "ID of the event to be refired.")
23+
command.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.")
24+
command.Flags().BoolVarP(&noConfig, "no-config", "D", false, "Disables the use of the configuration, if it exists.")
25+
command.MarkFlagRequired("id")
26+
27+
return
28+
}
29+
30+
func retriggerCmdRun(cmd *cobra.Command, args []string) error {
31+
if transport == "websub" {
32+
return fmt.Errorf(websubDeprecationNotice)
33+
}
34+
35+
defaults := configure_event.GetEventConfiguration(noConfig)
36+
37+
if secret != "" {
38+
if len(secret) < 10 || len(secret) > 100 {
39+
return fmt.Errorf("Invalid secret provided. Secrets must be between 10-100 characters")
40+
}
41+
} else {
42+
secret = defaults.Secret
43+
}
44+
45+
if forwardAddress == "" {
46+
if defaults.ForwardAddress == "" {
47+
return fmt.Errorf("if a default configuration is not set, forward-address must be provided")
48+
}
49+
forwardAddress = defaults.ForwardAddress
50+
}
51+
52+
res, err := trigger.RefireEvent(eventID, trigger.TriggerParameters{
53+
ForwardAddress: forwardAddress,
54+
Secret: secret,
55+
Timestamp: util.GetTimestamp().Format(time.RFC3339Nano),
56+
})
57+
if err != nil {
58+
return fmt.Errorf("Error refiring event: %s", err)
59+
}
60+
61+
fmt.Println(res)
62+
return nil
63+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package events
2+
3+
import "github.com/spf13/cobra"
4+
5+
func StartWebsocketServerCommand() *cobra.Command {
6+
return &cobra.Command{
7+
Use: "start-websocket-server",
8+
Deprecated: `use "twitch event websocket start-server" instead.`,
9+
}
10+
}

cmd/events/trigger.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package events
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
7+
"github.com/spf13/cobra"
8+
"github.com/twitchdev/twitch-cli/internal/events"
9+
configure_event "github.com/twitchdev/twitch-cli/internal/events/configure"
10+
"github.com/twitchdev/twitch-cli/internal/events/trigger"
11+
"github.com/twitchdev/twitch-cli/internal/events/types"
12+
)
13+
14+
func TriggerCommand() (command *cobra.Command) {
15+
command = &cobra.Command{
16+
Use: "trigger [event]",
17+
Short: "Creates mock events that can be forwarded to a local webserver for event testing.",
18+
Long: fmt.Sprintf(`Creates mock events that can be forwarded to a local webserver for event testing.
19+
Supported:
20+
%s`, types.AllWebhookTopics()),
21+
Args: cobra.MaximumNArgs(1),
22+
ValidArgs: types.AllWebhookTopics(),
23+
RunE: triggerCmdRun,
24+
Example: `twitch event trigger subscribe`,
25+
Aliases: []string{
26+
"fire", "emit",
27+
},
28+
}
29+
30+
// flags for forwarding functionality/changing payloads
31+
command.Flags().StringVarP(&forwardAddress, "forward-address", "F", "", "Forward address for mock event (webhook only).")
32+
command.Flags().StringVarP(&transport, "transport", "T", "webhook", fmt.Sprintf("Preferred transport method for event. Defaults to /EventSub.\nSupported values: %s", events.ValidTransports()))
33+
command.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.")
34+
command.Flags().BoolVarP(&noConfig, "no-config", "D", false, "Disables the use of the configuration, if it exists.")
35+
36+
// per-topic flags
37+
command.Flags().StringVarP(&toUser, "to-user", "t", "", "User ID of the receiver of the event. For example, the user that receives a follow. In most contexts, this is the broadcaster.")
38+
command.Flags().StringVarP(&fromUser, "from-user", "f", "", "User ID of the user sending the event, for example the user following another user.")
39+
command.Flags().StringVarP(&giftUser, "gift-user", "g", "", "Used only for \"gift\" events. Denotes the User ID of the gifting user.")
40+
command.Flags().BoolVarP(&isAnonymous, "anonymous", "a", false, "Denotes if the event is anonymous. Only applies to Gift and Sub events.")
41+
command.Flags().IntVarP(&count, "count", "c", 1, "Number of times to run an event. This can be used to simulate rapid events, such as multiple sub gift, or large number of cheers.")
42+
command.Flags().StringVarP(&eventStatus, "event-status", "S", "", "Status of the Event object (.event.status in JSON); currently applies to channel points redemptions.")
43+
command.Flags().StringVarP(&subscriptionStatus, "subscription-status", "r", "enabled", "Status of the Subscription object (.subscription.status in JSON). Defaults to \"enabled\".")
44+
command.Flags().StringVarP(&itemID, "item-id", "i", "", "Manually set the ID of the event payload item (for example the reward ID in redemption events). For stream events, this is the game ID.")
45+
command.Flags().StringVarP(&itemName, "item-name", "n", "", "Manually set the name of the event payload item (for example the reward ID in redemption events). For stream events, this is the game title.")
46+
command.Flags().Int64VarP(&cost, "cost", "C", 0, "Amount of drops, subscriptions, bits, or channel points redeemed/used in the event.")
47+
command.Flags().StringVarP(&description, "description", "d", "", "Title the stream should be updated with.")
48+
command.Flags().StringVarP(&gameID, "game-id", "G", "", "Sets the game/category ID for applicable events.")
49+
command.Flags().StringVarP(&tier, "tier", "", "", "Sets the subscription tier. Valid values are 1000, 2000, and 3000.")
50+
command.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
51+
command.Flags().StringVar(&timestamp, "timestamp", "", "Sets the timestamp to be used in payloads and headers. Must be in RFC3339Nano format.")
52+
command.Flags().IntVar(&charityCurrentValue, "charity-current-value", 0, "Only used for \"charity-*\" events. Manually set the current dollar value for charity events.")
53+
command.Flags().IntVar(&charityTargetValue, "charity-target-value", 1500000, "Only used for \"charity-*\" events. Manually set the target dollar value for charity events.")
54+
command.Flags().StringVar(&clientId, "client-id", "", "Manually set the Client ID used in revoke, grant, and bits transaction events.")
55+
command.Flags().StringVarP(&version, "version", "v", "", "Chooses the EventSub version used for a specific event. Not required for most events.")
56+
command.Flags().StringVar(&websocketClient, "session", "", "Defines a specific websocket client/session to forward an event to. Used only with \"websocket\" transport.")
57+
command.Flags().StringVar(&banStart, "ban-start", "", "Sets the timestamp a ban started at.")
58+
command.Flags().StringVar(&banEnd, "ban-end", "", "Sets the timestamp a ban is intended to end at. If not set, the ban event will appear as permanent. This flag can take a timestamp or relative time (600, 600s, 10d4h12m55s)")
59+
60+
return
61+
}
62+
63+
func triggerCmdRun(cmd *cobra.Command, args []string) error {
64+
if len(args) == 0 {
65+
cmd.Help()
66+
return fmt.Errorf("")
67+
}
68+
69+
if transport == "websub" {
70+
return fmt.Errorf(websubDeprecationNotice)
71+
}
72+
73+
defaults := configure_event.GetEventConfiguration(noConfig)
74+
75+
if secret != "" {
76+
if len(secret) < 10 || len(secret) > 100 {
77+
return fmt.Errorf("Invalid secret provided. Secrets must be between 10-100 characters")
78+
}
79+
} else {
80+
secret = defaults.Secret
81+
}
82+
83+
// Validate that the forward address is actually a URL
84+
if len(forwardAddress) > 0 {
85+
_, err := url.ParseRequestURI(forwardAddress)
86+
if err != nil {
87+
return err
88+
}
89+
} else {
90+
forwardAddress = defaults.ForwardAddress
91+
}
92+
93+
for i := 0; i < count; i++ {
94+
res, err := trigger.Fire(trigger.TriggerParameters{
95+
Event: args[0],
96+
EventID: eventID,
97+
Transport: transport,
98+
ForwardAddress: forwardAddress,
99+
FromUser: fromUser,
100+
ToUser: toUser,
101+
GiftUser: giftUser,
102+
Secret: secret,
103+
IsAnonymous: isAnonymous,
104+
EventStatus: eventStatus,
105+
ItemID: itemID,
106+
Cost: cost,
107+
Description: description,
108+
ItemName: itemName,
109+
GameID: gameID,
110+
Tier: tier,
111+
SubscriptionStatus: subscriptionStatus,
112+
Timestamp: timestamp,
113+
CharityCurrentValue: charityCurrentValue,
114+
CharityTargetValue: charityTargetValue,
115+
ClientID: clientId,
116+
Version: version,
117+
WebSocketClient: websocketClient,
118+
BanStartTimestamp: banStart,
119+
BanEndTimestamp: banEnd,
120+
})
121+
122+
if err != nil {
123+
return err
124+
}
125+
126+
fmt.Println(res)
127+
}
128+
129+
return nil
130+
}

cmd/events/variables.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package events
2+
3+
const websubDeprecationNotice = "Halt! It appears you are trying to use WebSub, which has been deprecated. For more information, see: https://discuss.dev.twitch.tv/t/deprecation-of-websub-based-webhooks/32152"
4+
5+
var (
6+
isAnonymous bool
7+
forwardAddress string
8+
transport string
9+
noConfig bool
10+
fromUser string
11+
toUser string
12+
giftUser string
13+
eventID string
14+
secret string
15+
eventStatus string
16+
subscriptionStatus string
17+
itemID string
18+
itemName string
19+
cost int64
20+
count int
21+
description string
22+
gameID string
23+
tier string
24+
timestamp string
25+
charityCurrentValue int
26+
charityTargetValue int
27+
clientId string
28+
version string
29+
websocketClient string
30+
banStart string
31+
banEnd string
32+
)

cmd/events/verify_subscription.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package events
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
"time"
7+
8+
"github.com/spf13/cobra"
9+
"github.com/twitchdev/twitch-cli/internal/events"
10+
configure_event "github.com/twitchdev/twitch-cli/internal/events/configure"
11+
"github.com/twitchdev/twitch-cli/internal/events/types"
12+
"github.com/twitchdev/twitch-cli/internal/events/verify"
13+
"github.com/twitchdev/twitch-cli/internal/util"
14+
)
15+
16+
func VerifySubscriptionCommand() (command *cobra.Command) {
17+
command = &cobra.Command{
18+
Use: "verify-subscription [event]",
19+
Short: "Mocks the subscription verification event. Can be forwarded to a local webserver for testing.",
20+
Long: fmt.Sprintf(`Mocks the subscription verification event that can be forwarded to a local webserver for testing.
21+
Supported:
22+
%s`, types.AllWebhookTopics()),
23+
Args: cobra.MaximumNArgs(1),
24+
ValidArgs: types.AllWebhookTopics(),
25+
RunE: verifyCmdRun,
26+
Example: `twitch event verify-subscription subscribe`,
27+
Aliases: []string{
28+
"verify",
29+
},
30+
}
31+
32+
command.Flags().StringVarP(&forwardAddress, "forward-address", "F", "", "Forward address for mock event (webhook only).")
33+
command.Flags().StringVarP(&transport, "transport", "T", "webhook", fmt.Sprintf("Preferred transport method for event. Defaults to EventSub.\nSupported values: %s", events.ValidTransports()))
34+
command.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.")
35+
command.Flags().StringVar(&timestamp, "timestamp", "", "Sets the timestamp to be used in payloads and headers. Must be in RFC3339Nano format.")
36+
command.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
37+
command.Flags().StringVarP(&version, "version", "v", "", "Chooses the EventSub version used for a specific event. Not required for most events.")
38+
command.Flags().BoolVarP(&noConfig, "no-config", "D", false, "Disables the use of the configuration, if it exists.")
39+
command.Flags().StringVarP(&toUser, "broadcaster", "b", "", "User ID of the broadcaster for the verification event.")
40+
41+
return
42+
}
43+
44+
func verifyCmdRun(cmd *cobra.Command, args []string) error {
45+
if len(args) == 0 {
46+
cmd.Help()
47+
return fmt.Errorf("")
48+
}
49+
50+
if transport == "websub" {
51+
return fmt.Errorf(websubDeprecationNotice)
52+
}
53+
54+
defaults := configure_event.GetEventConfiguration(noConfig)
55+
56+
if secret != "" {
57+
if len(secret) < 10 || len(secret) > 100 {
58+
return fmt.Errorf("Invalid secret provided. Secrets must be between 10-100 characters")
59+
}
60+
} else {
61+
secret = defaults.Secret
62+
}
63+
64+
// Validate that the forward address is actually a URL
65+
if len(forwardAddress) > 0 {
66+
_, err := url.ParseRequestURI(forwardAddress)
67+
if err != nil {
68+
return err
69+
}
70+
} else {
71+
forwardAddress = defaults.ForwardAddress
72+
}
73+
74+
if timestamp == "" {
75+
timestamp = util.GetTimestamp().Format(time.RFC3339Nano)
76+
} else {
77+
// Verify custom timestamp
78+
_, err := time.Parse(time.RFC3339Nano, timestamp)
79+
if err != nil {
80+
return fmt.Errorf(
81+
`Discarding verify: Invalid timestamp provided.
82+
Please follow RFC3339Nano, which is used by Twitch as seen here:
83+
https://dev.twitch.tv/docs/eventsub/handling-webhook-events#processing-an-event`)
84+
}
85+
}
86+
87+
_, err := verify.VerifyWebhookSubscription(verify.VerifyParameters{
88+
Event: args[0],
89+
Transport: transport,
90+
ForwardAddress: forwardAddress,
91+
Secret: secret,
92+
Timestamp: timestamp,
93+
EventID: eventID,
94+
BroadcasterUserID: toUser,
95+
Version: version,
96+
})
97+
98+
if err != nil {
99+
return err
100+
}
101+
102+
return nil
103+
}

0 commit comments

Comments
 (0)