Skip to content

Commit

Permalink
Add Chat Badges features
Browse files Browse the repository at this point in the history
From twitch changelog : 2021‑05‑28

Twitch API endpoints have been added for chat badges.
 - Get Channel Chat Badges
 - Get Global Chat Badges

EventSub subscription type updates.
 - Channel Subscription End has moved from public beta to v1. This was previously known as “Channel Unsubscribe.”
 - Channel Subscription Gift has been added as a public beta.
  • Loading branch information
Scorfly committed May 30, 2021
1 parent e8d6ace commit 91cf41b
Show file tree
Hide file tree
Showing 5 changed files with 270 additions and 1 deletion.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
59 changes: 59 additions & 0 deletions chat.go
Original file line number Diff line number Diff line change
@@ -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
}
151 changes: 151 additions & 0 deletions chat_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
43 changes: 43 additions & 0 deletions docs/chat_docs.md
Original file line number Diff line number Diff line change
@@ -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)
```
16 changes: 15 additions & 1 deletion eventsub.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"`
Expand Down

0 comments on commit 91cf41b

Please sign in to comment.