diff --git a/README.md b/README.md index 0f22a37..10dd5cc 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ looking for the Twitch API docs, see the [Twitch Developer website](https://dev. - [ ] Get Custom Reward Redemption - [ ] Update Custom Reward - [ ] Update Redemption Status +- [x] Get Channel Chat Badges +- [x] Get Global Chat Badges - [x] Create Clip - [x] Get Clip - [x] Get Clips diff --git a/chat.go b/chat.go new file mode 100644 index 0000000..69f6b04 --- /dev/null +++ b/chat.go @@ -0,0 +1,59 @@ +package helix + +// GetChatBadgeParams ... +type GetChatBadgeParams struct { + BroadcasterID string `query:"broadcaster_id"` +} + +// GetChatBadgeResponse ... +type GetChatBadgeResponse struct { + ResponseCommon + Data ManyChatBadge +} + +// ManyChatBadge ... +type ManyChatBadge struct { + Badges []ChatBadge `json:"data"` +} + +// ChatBadge ... +type ChatBadge struct { + SetID string `json:"set_id"` + Versions []BadgeVersion `json:"versions"` +} + +// BadgeVersion ... +type BadgeVersion struct { + ID string `json:"id"` + ImageUrl1x string `json:"image_url_1x"` + ImageUrl2x string `json:"image_url_2x"` + ImageUrl4x string `json:"image_url_4x"` +} + +// GetChannelChatBadges ... +func (c *Client) GetChannelChatBadges(params *GetChatBadgeParams) (*GetChatBadgeResponse, error) { + resp, err := c.get("/chat/badges", &ManyChatBadge{}, params) + if err != nil { + return nil, err + } + + channels := &GetChatBadgeResponse{} + resp.HydrateResponseCommon(&channels.ResponseCommon) + channels.Data.Badges = resp.Data.(*ManyChatBadge).Badges + + return channels, nil +} + +// GetGlobalChatBadges ... +func (c *Client) GetGlobalChatBadges() (*GetChatBadgeResponse, error) { + resp, err := c.get("/chat/badges/global", &ManyChatBadge{}, nil) + if err != nil { + return nil, err + } + + channels := &GetChatBadgeResponse{} + resp.HydrateResponseCommon(&channels.ResponseCommon) + channels.Data.Badges = resp.Data.(*ManyChatBadge).Badges + + return channels, nil +} diff --git a/chat_test.go b/chat_test.go new file mode 100644 index 0000000..3355efe --- /dev/null +++ b/chat_test.go @@ -0,0 +1,151 @@ +package helix + +import ( + "net/http" + "testing" +) + +func TestGetChannelChatBadges(t *testing.T) { + t.Parallel() + + testCases := []struct { + statusCode int + options *Options + GetChatBadgeParams *GetChatBadgeParams + respBody string + }{ + { + http.StatusBadRequest, + &Options{ClientID: "my-client-id"}, + &GetChatBadgeParams{BroadcasterID: ""}, + `{"error":"Bad Request","status":400,"message":"Missing required parameter \"broadcaster_id\""}`, + }, + { + http.StatusOK, + &Options{ClientID: "my-client-id"}, + &GetChatBadgeParams{BroadcasterID: "121445595"}, + `{"data": [{"set_id": "bits","versions": [{"id": "1","image_url_1x": "https://static-cdn.jtvnw.net/badges/v1/743a0f3b-84b3-450b-96a0-503d7f4a9764/1","image_url_2x": "https://static-cdn.jtvnw.net/badges/v1/743a0f3b-84b3-450b-96a0-503d7f4a9764/2","image_url_4x": "https://static-cdn.jtvnw.net/badges/v1/743a0f3b-84b3-450b-96a0-503d7f4a9764/3"}]},{"set_id": "subscriber","versions": [{"id": "0","image_url_1x": "https://static-cdn.jtvnw.net/badges/v1/eb4a8a4c-eacd-4f5e-b9f2-394348310442/1","image_url_2x": "https://static-cdn.jtvnw.net/badges/v1/eb4a8a4c-eacd-4f5e-b9f2-394348310442/2","image_url_4x": "https://static-cdn.jtvnw.net/badges/v1/eb4a8a4c-eacd-4f5e-b9f2-394348310442/3"}]}]}`, + }, + } + + for _, testCase := range testCases { + c := newMockClient(testCase.options, newMockHandler(testCase.statusCode, testCase.respBody, nil)) + + resp, err := c.GetChannelChatBadges(testCase.GetChatBadgeParams) + 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 + } + } + + // Test with HTTP Failure + options := &Options{ + ClientID: "my-client-id", + HTTPClient: &badMockHTTPClient{ + newMockHandler(0, "", nil), + }, + } + c := &Client{ + opts: options, + } + + _, err := c.GetChannelChatBadges(&GetChatBadgeParams{}) + 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") + } +} + +func TestGetGlobalChatBadges(t *testing.T) { + t.Parallel() + + testCases := []struct { + statusCode int + options *Options + respBody string + }{ + { + http.StatusUnauthorized, + &Options{ClientID: "my-client-id"}, + `{"error":"Unauthorized","status":401,"message":"OAuth token is missing"}`, + }, + { + http.StatusOK, + &Options{ClientID: "my-client-id", UserAccessToken: "my-user-access-token"}, + `{"data": [{"set_id": "vip","versions": [{"id": "1","image_url_1x": "https://static-cdn.jtvnw.net/badges/v1/b817aba4-fad8-49e2-b88a-7cc744dfa6ec/1","image_url_2x": "https://static-cdn.jtvnw.net/badges/v1/b817aba4-fad8-49e2-b88a-7cc744dfa6ec/2","image_url_4x": "https://static-cdn.jtvnw.net/badges/v1/b817aba4-fad8-49e2-b88a-7cc744dfa6ec/3"}]}]}`, + }, + } + + for _, testCase := range testCases { + c := newMockClient(testCase.options, newMockHandler(testCase.statusCode, testCase.respBody, nil)) + + resp, err := c.GetGlobalChatBadges() + 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.StatusUnauthorized { + if resp.Error != "Unauthorized" { + t.Errorf("expected error to be %s, got %s", "Unauthorized", resp.Error) + } + + if resp.ErrorStatus != http.StatusUnauthorized { + t.Errorf("expected error status to be %d, got %d", http.StatusBadRequest, resp.ErrorStatus) + } + + expectedErrMsg := "OAuth token is missing" + if resp.ErrorMessage != expectedErrMsg { + t.Errorf("expected error message to be %s, got %s", expectedErrMsg, resp.ErrorMessage) + } + + continue + } + } + + // Test with HTTP Failure + options := &Options{ + ClientID: "my-client-id", + HTTPClient: &badMockHTTPClient{ + newMockHandler(0, "", nil), + }, + } + c := &Client{ + opts: options, + } + + _, err := c.GetGlobalChatBadges() + 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") + } +} diff --git a/docs/chat_docs.md b/docs/chat_docs.md new file mode 100644 index 0000000..277359d --- /dev/null +++ b/docs/chat_docs.md @@ -0,0 +1,43 @@ +# Chat Documentation + +## Get Channel Chat Badges + +This is an example of how to get channel chat badges + +```go +client, err := helix.NewClient(&helix.Options{ + ClientID: "your-client-id", +}) +if err != nil { + // handle error +} + +resp, err := client.GetChannelChatBadges(&helix.GetChatBadgeParams{ + BroadcasterID: "145328278", +}) +if err != nil { + // handle error +} + +fmt.Printf("%+v\n", resp) +``` + +## Get Global Chat Badges + +This is an example of how to get global chat badges + +```go +client, err := helix.NewClient(&helix.Options{ + ClientID: "your-client-id", +}) +if err != nil { + // handle error +} + +resp, err := client.GetGlobalChatBadges() +if err != nil { + // handle error +} + +fmt.Printf("%+v\n", resp) +``` diff --git a/eventsub.go b/eventsub.go index 751a57e..8e31431 100644 --- a/eventsub.go +++ b/eventsub.go @@ -82,7 +82,8 @@ const ( EventSubTypeChannelUpdate = "channel.update" EventSubTypeChannelFollow = "channel.follow" EventSubTypeChannelSubscription = "channel.subscribe" - EventSubTypeChannelUnsubscribe = "channel.unsubscribe" /* beta */ + EventSubTypeChannelSubscriptionEnd = "channel.subscription.end" + EventSubTypeChannelSubscriptionGift = "channel.subscription.gift" /* beta */ EventSubTypeChannelCheer = "channel.cheer" EventSubTypeChannelRaid = "channel.raid" EventSubTypeChannelBan = "channel.ban" @@ -141,6 +142,19 @@ type EventSubChannelSubscribeEvent struct { IsGift bool `json:"is_gift"` } +// EventSubChannelSubscriptionGiftEvent +type EventSubChannelSubscriptionGiftEvent struct { + UserID string `json:"user_id"` + UserLogin string `json:"user_login"` + UserName string `json:"user_name"` + BroadcasterUserID string `json:"broadcaster_user_id"` + BroadcasterUserLogin string `json:"broadcaster_user_login"` + BroadcasterUserName string `json:"broadcaster_user_name"` + Total int `json:"total"` + Tier string `json:"tier"` + CumulativeTotal int `json:"cumulative_total"` +} + // Data for a channel cheer notification type EventSubChannelCheerEvent struct { IsAnonymous bool `json:"is_anonymous"`