Repository: nicklaw5/helix
Pull request #141 (2022-08-30 23:36:14)

feat: Charity Campaigns & Charity Donation Events
- support for new Twitch endpoint 'GetCharityCampaigns'
- Add unit test for GetCharityCampaigns
- support for Charity Donation Twitch Eventsub Events.


Patch:
diff --git a/charity.go b/charity.go
new file mode 100644
index 0000000..1194858
--- /dev/null
+++ b/charity.go
@@ -0,0 +1,51 @@
+package helix
+
+type CharityCampaignAmount struct {
+	Value         int64  `json:"value"`
+	DecimalPlaces int64  `json:"decimal_places"`
+	Currency      string `json:"currency"`
+}
+
+type CharityCampaignData struct {
+	ID               string                `json:"id"`
+	BroadcasterID    string                `json:"broadcaster_id"`
+	BroadcasterName  string                `json:"broadcaster_name"`
+	BroadcasterLogin string                `json:"broadcaster_login"`
+	Name             string                `json:"charity_name"`
+	Description      string                `json:"charity_description"`
+	LogoUrl          string                `json:"charity_logo"`
+	WebsiteUrl       string                `json:"charity_website"`
+	TargetAmount     CharityCampaignAmount `json:"target_amount"`
+	CurrentAmount    CharityCampaignAmount `json:"current_amount"`
+}
+
+type ManyCharityCampaigns struct {
+	Campaigns  []CharityCampaignData `json:"data"`
+	Pagination Pagination            `json:"pagination"`
+}
+
+type CharityCampaignsResponse struct {
+	ResponseCommon
+	Data ManyCharityCampaigns
+}
+
+type CharityCampaignsParams struct {
+	BroadcasterID string `query:"broadcaster_id"`
+	After         string `query:"after"`
+	First         int    `query:"first,20"` // Limit 100
+}
+
+// Required scope: channel:read:charity
+func (c *Client) GetCharityCampaigns(params *CharityCampaignsParams) (*CharityCampaignsResponse, error) {
+	resp, err := c.get("charity/campaigns", &ManyCharityCampaigns{}, params)
+	if err != nil {
+		return nil, err
+	}
+
+	events := &CharityCampaignsResponse{}
+	resp.HydrateResponseCommon(&events.ResponseCommon)
+	events.Data.Campaigns = resp.Data.(*ManyCharityCampaigns).Campaigns
+	events.Data.Pagination = resp.Data.(*ManyCharityCampaigns).Pagination
+
+	return events, nil
+}
diff --git a/eventsub.go b/eventsub.go
index 80e59f3..4b72c7b 100644
--- a/eventsub.go
+++ b/eventsub.go
@@ -115,6 +115,7 @@ const (
 	EventSubTypeHypeTrainBegin                            = "channel.hype_train.begin"
 	EventSubTypeHypeTrainProgress                         = "channel.hype_train.progress"
 	EventSubTypeHypeTrainEnd                              = "channel.hype_train.end"
+	EventSubTypeCharityDonation                           = "channel.charity_campaign.donate"
 	EventSubTypeStreamOnline                              = "stream.online"
 	EventSubTypeStreamOffline                             = "stream.offline"
 	EventSubTypeUserAuthorizationRevoke                   = "user.authorization.revoke"
@@ -568,6 +569,23 @@ type EventSubChannelGoalEndEvent struct {
 	EndedAt              Time   `json:"ended_at"`
 }
 
+type EventSubCharityAmount struct {
+	Value         int64  `json:"value"`
+	DecimalPlaces int64  `json:"decimal_places"`
+	Currency      string `json:"currency"`
+}
+
+type EventSubCharityDonationEvent struct {
+	ID                   string                `json:"id"`
+	BroadcasterUserID    string                `json:"broadcaster_user_id"`
+	BroadcasterUserName  string                `json:"broadcaster_user_name"`
+	BroadcasterUserLogin string                `json:"broadcaster_user_login"`
+	UserID               string                `json:"user_id"`
+	UserName             string                `json:"user_name"`
+	UserLogin            string                `json:"user_login"`
+	Amount               EventSubCharityAmount `json:"amount"`
+}
+
 // Get all EventSub Subscriptions
 func (c *Client) GetEventSubSubscriptions(params *EventSubSubscriptionsParams) (*EventSubSubscriptionsResponse, error) {
 	resp, err := c.get("/eventsub/subscriptions", &ManyEventSubSubscriptions{}, params)


Test patch:
diff --git a/charity_test.go b/charity_test.go
new file mode 100644
index 0000000..1a25201
--- /dev/null
+++ b/charity_test.go
@@ -0,0 +1,86 @@
+package helix
+
+import (
+	"net/http"
+	"testing"
+)
+
+func TestGetCharityCampaigns(t *testing.T) {
+	t.Parallel()
+
+	testCases := []struct {
+		statusCode  int
+		options     *Options
+		PollsParams *CharityCampaignsParams
+		respBody    string
+	}{
+		{
+			http.StatusBadRequest,
+			&Options{ClientID: "my-client-id"},
+			&CharityCampaignsParams{BroadcasterID: ""},
+			`{"error":"Bad Request","status":400,"message":"Missing required parameter \"broadcaster_id\""}`,
+		},
+		{
+			http.StatusOK,
+			&Options{ClientID: "my-client-id"},
+			&CharityCampaignsParams{BroadcasterID: "123456"},
+			`{ "data": [{ "id": "123-abc-456-def", "broadcaster_id": "123456", "broadcaster_name": "SunnySideUp", "broadcaster_login": "sunnysideup", "charity_name": "Example name", "charity_description": "Example description", "charity_logo": "https://example.url/logo.png", "charity_website": "https://www.example.com", "current_amount": { "value": 86000, "decimal_places": 2, "currency": "USD" }, "target_amount": { "value": 1500000, "decimal_places": 2, "currency": "USD" } }] }`},
+	}
+
+	for _, testCase := range testCases {
+		c := newMockClient(testCase.options, newMockHandler(testCase.statusCode, testCase.respBody, nil))
+
+		resp, err := c.GetCharityCampaigns(testCase.PollsParams)
+		if err != nil {
+			t.Error(err)
+		}
+
+		if resp.StatusCode != testCase.statusCode {
+			t.Errorf("expected status code to be %d, got %d", testCase.statusCode, resp.StatusCode)
+		}
+
+		if resp.StatusCode == http.StatusBadRequest {
+			if resp.Error != "Bad Request" {
+				t.Errorf("expected error to be %s, got %s", "Bad Request", resp.Error)
+			}
+
+			if resp.ErrorStatus != http.StatusBadRequest {
+				t.Errorf("expected error status to be %d, got %d", http.StatusBadRequest, resp.ErrorStatus)
+			}
+
+			expectedErrMsg := "Missing required parameter \"broadcaster_id\""
+			if resp.ErrorMessage != expectedErrMsg {
+				t.Errorf("expected error message to be %s, got %s", expectedErrMsg, resp.ErrorMessage)
+			}
+
+			continue
+		}
+
+		if len(resp.Data.Campaigns) != 1 {
+			t.Errorf("expected charity campaigns len to be 1, got %d", len(resp.Data.Campaigns))
+		}
+		if resp.Data.Campaigns[0].Name != "Example name" {
+			t.Errorf("invalid charity name %q, expected Example name", resp.Data.Campaigns[0].Name)
+		}
+	}
+
+	// Test with HTTP Failure
+	options := &Options{
+		ClientID: "my-client-id",
+		HTTPClient: &badMockHTTPClient{
+			newMockHandler(0, "", nil),
+		},
+	}
+	c := &Client{
+		opts: options,
+	}
+
+	_, err := c.GetCharityCampaigns(&CharityCampaignsParams{})
+	if err == nil {
+		t.Error("expected error but got nil")
+	}
+
+	if err.Error() != "Failed to execute API request: Oops, that's bad :(" {
+		t.Error("expected error does match return error")
+	}
+}