Skip to content

Commit

Permalink
Merge pull request #257 from twitchdev/fix-195
Browse files Browse the repository at this point in the history
Allow perma ban testing for channel.ban
  • Loading branch information
Xemdo authored Aug 9, 2023
2 parents 5dd5d07 + ef18678 commit 2f6df3b
Show file tree
Hide file tree
Showing 10 changed files with 306 additions and 45 deletions.
6 changes: 6 additions & 0 deletions cmd/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ var (
clientId string
version string
websocketClient string
banStart string
banEnd string
)

// websocketCmd-specific flags
Expand Down Expand Up @@ -158,6 +160,8 @@ func init() {
triggerCmd.Flags().StringVar(&clientId, "client-id", "", "Manually set the Client ID used in revoke, grant, and bits transaction events.")
triggerCmd.Flags().StringVarP(&version, "version", "v", "", "Chooses the EventSub version used for a specific event. Not required for most events.")
triggerCmd.Flags().StringVar(&websocketClient, "session", "", "Defines a specific websocket client/session to forward an event to. Used only with \"websocket\" transport.")
triggerCmd.Flags().StringVar(&banStart, "ban-start", "", "Sets the timestamp a ban started at.")
triggerCmd.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)")

// retrigger flags
retriggerCmd.Flags().StringVarP(&forwardAddress, "forward-address", "F", "", "Forward address for mock event (webhook only).")
Expand Down Expand Up @@ -237,6 +241,8 @@ func triggerCmdRun(cmd *cobra.Command, args []string) error {
ClientID: clientId,
Version: version,
WebSocketClient: websocketClient,
BanStartTimestamp: banStart,
BanEndTimestamp: banEnd,
})

if err != nil {
Expand Down
4 changes: 3 additions & 1 deletion docs/event.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,10 @@ This command can take either the Event or Alias listed as an argument. It is pre
| Flag | Shorthand | Description | Example | Required? (Y/N) |
|---------------------------|-----------|---------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------|-----------------|
| `--anonymous` | `-a` | Denotes if the event is anonymous. Only applies to Gift and Sub events. | `-a` | N |
| `--ban-end` | | Sets the timestamp a ban is intended to end at. If not set, the ban event will appear as permanent. | `--ban-end 10d20h12m35s` | N |
| `--ban-start` | | Sets the timestamp a ban started at. | `--ban-start 2017-04-13T14:34:23` | N |
| `--charity-current-value` | | For charity events, manually set the charity dollar value. | `--charity-current-value 11000` | N |
| `--charity-target-value` | | For charity events, manually set the charity dollar value. | `--charity-current-value 23400` | N |
| `--charity-target-value` | | Only used for "charity-*" events. Manually set the target dollar value for charity events. (default 1500000) | `--charity-target-value 23400` | N |
| `--client-id` | | Manually set the Client ID used for revoke, grant, and bits transactions. | `--client-id 4ofh8m0706jqpholgk00u3xvb4spct` | N |
| `--cost` | `-C` | Amount of subscriptions, bits, or channel points redeemed/used in the event. | `-C 250` | N |
| `--count` | `-c` | Count of events to fire. This can be used to simulate an influx of events. | `-c 100` | N |
Expand Down
3 changes: 2 additions & 1 deletion internal/events/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ type MockEventParameters struct {
ItemID string
ItemName string
Cost int64
IsPermanent bool
Description string
GameID string
Tier string
Timestamp string
CharityCurrentValue int
CharityTargetValue int
ClientID string
BanStartTimestamp string
BanEndTimestamp string
}

type MockEventResponse struct {
Expand Down
4 changes: 4 additions & 0 deletions internal/events/trigger/trigger_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ type TriggerParameters struct {
ClientID string
Version string
WebSocketClient string
BanStartTimestamp string
BanEndTimestamp string
}

type TriggerResponse struct {
Expand Down Expand Up @@ -135,6 +137,8 @@ https://dev.twitch.tv/docs/eventsub/handling-webhook-events#processing-an-event`
CharityTargetValue: p.CharityTargetValue,
ClientID: p.ClientID,
GiftUser: p.GiftUser,
BanStartTimestamp: p.BanStartTimestamp,
BanEndTimestamp: p.BanEndTimestamp,
}

e, err := types.GetByTriggerAndTransportAndVersion(p.Event, p.Transport, p.Version)
Expand Down
75 changes: 57 additions & 18 deletions internal/events/types/ban/ban.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package ban

import (
"encoding/json"
"regexp"
"strconv"
"strings"
"time"

Expand All @@ -17,16 +19,14 @@ var transportsSupported = map[string]bool{
models.TransportWebSocket: true,
}

var triggerSupported = []string{"ban", "unban"}
var triggerSupported = []string{"ban"}

var triggerMapping = map[string]map[string]string{
models.TransportWebhook: {
"ban": "channel.ban",
"unban": "channel.unban",
"ban": "channel.ban",
},
models.TransportWebSocket: {
"ban": "channel.ban",
"unban": "channel.unban",
"ban": "channel.ban",
},
}

Expand All @@ -50,25 +50,64 @@ func (e Event) GenerateEvent(params events.MockEventParameters) (events.MockEven
ModeratorUserName: "CLIModerator",
}

if params.Trigger == "ban" {
reason := "This is a test event"
bannedAt := params.Timestamp
var endsAt *string = nil
reason := "This is a test event"

if !params.IsPermanent {
// Timeout uses channel.ban as well, which is why IsPermanent exists
// Set ends_at to be 3 minutes in the future
// This event supports --timestamp historically, but is overridden by the newer --ban-start
bannedAt := params.Timestamp
if params.BanStartTimestamp != "" {
bannedAt = params.BanStartTimestamp
}

var endsAt *string = nil
var isPermanent bool

if params.BanEndTimestamp == "" {
// Default to perma ban
isPermanent = true
} else {
r1 := regexp.MustCompile("^[0-9]+$")
r2 := regexp.MustCompile("^(?:(?P<Days>[0-9]+)[dD])?(?:(?P<Hours>[0-9]+)[hH])?(?:(?P<Minutes>[0-9]+)[mM])?(?:(?P<Seconds>[0-9]+)[sS])?$")

if r1.MatchString(params.BanEndTimestamp) {
// Similar format to /timeout <user> <seconds>
// twitch event trigger channel.ban --ban-end=600
seconds, _ := strconv.Atoi(r1.FindAllString(params.BanEndTimestamp, -1)[0])
tNow, _ := time.Parse(time.RFC3339Nano, params.Timestamp)
tLater := tNow.Add(time.Minute * 10).Format(time.RFC3339Nano)
tLater := tNow.Add(time.Duration(seconds) * time.Second).Format(time.RFC3339Nano)
endsAt = &tLater
}
isPermanent = false

} else if r2.MatchString(params.BanEndTimestamp) {
// Relative time specified by shorthands. e.g. 90d10h30m45s
// Can include or exclude any of those, but they have to be in the same order as above
values := r2.FindStringSubmatch(params.BanEndTimestamp)
days, _ := strconv.Atoi(values[r2.SubexpIndex("Days")])
hours, _ := strconv.Atoi(values[r2.SubexpIndex("Hours")])
minutes, _ := strconv.Atoi(values[r2.SubexpIndex("Minutes")])
seconds, _ := strconv.Atoi(values[r2.SubexpIndex("Seconds")])

ban.Reason = &reason
ban.BannedAt = &bannedAt
ban.EndsAt = endsAt
ban.IsPermanent = &params.IsPermanent
tNow, _ := time.Parse(time.RFC3339Nano, params.Timestamp)
tLater := tNow.Add(time.Duration(days*24) * time.Hour).
Add(time.Duration(hours) * time.Hour).
Add(time.Duration(minutes) * time.Minute).
Add(time.Duration(seconds) * time.Second).
Format(time.RFC3339Nano)
endsAt = &tLater
isPermanent = false

} else {
// Timeout with user provided timestamp
endsAt = &params.BanEndTimestamp
isPermanent = false

}
}

ban.Reason = reason
ban.BannedAt = bannedAt
ban.EndsAt = endsAt
ban.IsPermanent = isPermanent

body := *&models.EventsubResponse{
Subscription: models.EventsubSubscription{
ID: params.ID,
Expand Down
21 changes: 0 additions & 21 deletions internal/events/types/ban/ban_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,6 @@ func TestEventSubBan(t *testing.T) {

a.Equal(toUser, body.Event.BroadcasterUserID, "Expected to user %v, got %v", toUser, body.Event.BroadcasterUserID)
a.Equal(fromUser, body.Event.UserID, "Expected from user %v, got %v", r.ToUser, body.Event.UserID)

// test for unban
params = events.MockEventParameters{
FromUserID: fromUser,
ToUserID: toUser,
Transport: models.TransportWebhook,
Trigger: "unban",
SubscriptionStatus: "enabled",
}

r, err = Event{}.GenerateEvent(params)
a.Nil(err)

err = json.Unmarshal(r.JSON, &body)
a.Nil(err)

a.Equal(toUser, body.Event.BroadcasterUserID, "Expected to user %v, got %v", toUser, body.Event.BroadcasterUserID)
a.Equal(fromUser, body.Event.UserID, "Expected from user %v, got %v", fromUser, body.Event.UserID)
}

func TestFakeTransport(t *testing.T) {
Expand All @@ -75,9 +57,6 @@ func TestValidTrigger(t *testing.T) {
r := Event{}.ValidTrigger("ban")
a.Equal(true, r)

r = Event{}.ValidTrigger("unban")
a.Equal(true, r)

r = Event{}.ValidTrigger("notban")
a.Equal(false, r)
}
Expand Down
2 changes: 2 additions & 0 deletions internal/events/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/twitchdev/twitch-cli/internal/events/types/streamup"
"github.com/twitchdev/twitch-cli/internal/events/types/subscribe"
"github.com/twitchdev/twitch-cli/internal/events/types/subscription_message"
"github.com/twitchdev/twitch-cli/internal/events/types/unban"
user_update "github.com/twitchdev/twitch-cli/internal/events/types/user"
"github.com/twitchdev/twitch-cli/internal/models"
)
Expand Down Expand Up @@ -67,6 +68,7 @@ func AllEvents() []events.MockEvent {
streamdown.Event{},
subscribe.Event{},
subscription_message.Event{},
unban.Event{},
user_update.Event{},
}
}
Expand Down
136 changes: 136 additions & 0 deletions internal/events/types/unban/unban.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package unban

import (
"encoding/json"
"strings"

"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.TransportWebhook: true,
models.TransportWebSocket: true,
}

var triggerSupported = []string{"unban"}

var triggerMapping = map[string]map[string]string{
models.TransportWebhook: {
"unban": "channel.unban",
},
models.TransportWebSocket: {
"unban": "channel.unban",
},
}

type Event struct{}

func (e Event) GenerateEvent(params events.MockEventParameters) (events.MockEventResponse, error) {
var event []byte
var err error

switch params.Transport {
case models.TransportWebhook, models.TransportWebSocket:
ban := models.UnbanEventSubEvent{
UserID: params.FromUserID,
UserLogin: params.FromUserName,
UserName: params.FromUserName,
BroadcasterUserID: params.ToUserID,
BroadcasterUserLogin: params.ToUserName,
BroadcasterUserName: params.ToUserName,
ModeratorUserId: util.RandomUserID(),
ModeratorUserLogin: "CLIModerator",
ModeratorUserName: "CLIModerator",
}

body := *&models.EventsubResponse{
Subscription: models.EventsubSubscription{
ID: params.ID,
Status: params.SubscriptionStatus,
Type: triggerMapping[params.Transport][params.Trigger],
Version: e.SubscriptionVersion(),
Condition: models.EventsubCondition{
BroadcasterUserID: params.ToUserID,
},
Transport: models.EventsubTransport{
Method: "webhook",
Callback: "null",
},
Cost: 0,
CreatedAt: params.Timestamp,
},
Event: ban,
}

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,
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]
}
func (e Event) GetAllTopicsByTransport(transport string) []string {
allTopics := []string{}
for _, topic := range triggerMapping[transport] {
allTopics = append(allTopics, topic)
}
return allTopics
}
func (e Event) GetEventSubAlias(t string) string {
// check for aliases
for trigger, topic := range triggerMapping[models.TransportWebhook] {
if topic == t {
return trigger
}
}
return ""
}

func (e Event) SubscriptionVersion() string {
return "1"
}
Loading

0 comments on commit 2f6df3b

Please sign in to comment.