Skip to content

Commit

Permalink
Merge pull request #223 from dx9er/feat/getmoderatedchannels
Browse files Browse the repository at this point in the history
feat: Implement Get Moderated Channels endpoint
  • Loading branch information
nicklaw5 authored Jun 29, 2024
2 parents e1f84cf + 7916a0c commit 22863cc
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 0 deletions.
1 change: 1 addition & 0 deletions SUPPORTED_ENDPOINTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
- [x] Add Blocked Term
- [x] Remove Blocked Term
- [x] Delete Chat Messages
- [x] Get Moderated Channels
- [x] Get Moderators
- [x] Add Channel Moderator
- [x] Remove Channel Moderator
Expand Down
26 changes: 26 additions & 0 deletions docs/moderation_docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,3 +288,29 @@ if err != nil {

fmt.Printf("%+v\n", resp)
```

## Get Moderated Channels

To use this function you need a user access token with the `user:read:moderated_channels` scope.
`UserID` is required and must match the user ID of the user access token.

This is an example of how to get channels the user has moderator privileges in.

```go
client, err := helix.NewClient(&helix.Options{
ClientID: "your-client-id",
UserAccessToken: "your-user-access-token",
})
if err != nil {
// handle error
}

resp, err := client.GetModeratedChannels(&helix.GetModeratedChannelsParams{
UserID: "154315414",
})
if err != nil {
// handle error
}

fmt.Printf("%+v\n", resp)
```
46 changes: 46 additions & 0 deletions moderation.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,3 +380,49 @@ func (c *Client) RemoveChannelModerator(params *RemoveChannelModeratorParams) (*

return moderators, nil
}

// `UserID` must match the user ID in the User-Access token
type GetModeratedChannelsParams struct {
// Required
UserID string `query:"user_id"`

// Optional
After string `query:"after"`
First int `query:"first"`
}

type ModeratedChannel struct {
BroadcasterID string `json:"broadcaster_id"`
BroadcasterLogin string `json:"broadcaster_login"`
BroadcasterName string `json:"broadcaster_name"`
}

type ManyModeratedChannels struct {
ModeratedChannels []ModeratedChannel `json:"data"`
Pagination Pagination `json:"pagination"`
}

type GetModeratedChannelsResponse struct {
ResponseCommon
Data ManyModeratedChannels
}

// GetModeratedChannels Gets a list of channels that the specified user has moderator privileges in.
// Required scope: user:read:moderated_channels
func (c *Client) GetModeratedChannels(params *GetModeratedChannelsParams) (*GetModeratedChannelsResponse, error) {
if params.UserID == "" {
return nil, errors.New("user id is required")
}

resp, err := c.get("/moderation/channels", &ManyModeratedChannels{}, params)
if err != nil {
return nil, err
}

moderatedChannels := &GetModeratedChannelsResponse{}
resp.HydrateResponseCommon(&moderatedChannels.ResponseCommon)
moderatedChannels.Data.ModeratedChannels = resp.Data.(*ManyModeratedChannels).ModeratedChannels
moderatedChannels.Data.Pagination = resp.Data.(*ManyModeratedChannels).Pagination

return moderatedChannels, nil
}
168 changes: 168 additions & 0 deletions moderation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -949,3 +949,171 @@ func TestRemoveChannelModerator(t *testing.T) {
t.Error("expected error does match return error")
}
}

func TestGetModeratedChannels(t *testing.T) {
t.Parallel()

testCases := []struct {
statusCode int
options *Options
params *GetModeratedChannelsParams
respBody string
parsed *ManyModeratedChannels
errorMsg string
}{
{
http.StatusOK,
&Options{ClientID: "my-client-id", UserAccessToken: "moderatedchannels-access-token"},
&GetModeratedChannelsParams{UserID: "154315414", First: 2},
`{
"data": [
{
"broadcaster_id": "183094685",
"broadcaster_login": "spaceashes",
"broadcaster_name": "spaceashes"
},
{
"broadcaster_id": "113944563",
"broadcaster_login": "reapex_1",
"broadcaster_name": "Reapex_1"
}
],
"pagination": {
"cursor": "eyJiIjpudWxsLCJhIjp7IkN1cnNvciI6ImV5SjBjQ0k2SW5WelpYSTZNVFUwTXpFMU5ERTBPbTF2WkdWeVlYUmxjeUlzSW5Seklqb2lZMmhoYm01bGJEb3hNVE01TkRRMU5qTWlMQ0pwY0NJNkluVnpaWEk2TVRVME16RTFOREUwT20xdlpHVnlZWFJsY3lJc0ltbHpJam9pTVRjeE5EVXhNelF4T0RFNE9UTXlPREV4TnlKOSJ9fQ"
}
}`,
&ManyModeratedChannels{
ModeratedChannels: []ModeratedChannel{
{
BroadcasterID: "183094685",
BroadcasterLogin: "spaceashes",
BroadcasterName: "spaceashes",
},
{
BroadcasterID: "113944563",
BroadcasterLogin: "reapex_1",
BroadcasterName: "Reapex_1",
},
},
Pagination: Pagination{
Cursor: "eyJiIjpudWxsLCJhIjp7IkN1cnNvciI6ImV5SjBjQ0k2SW5WelpYSTZNVFUwTXpFMU5ERTBPbTF2WkdWeVlYUmxjeUlzSW5Seklqb2lZMmhoYm01bGJEb3hNVE01TkRRMU5qTWlMQ0pwY0NJNkluVnpaWEk2TVRVME16RTFOREUwT20xdlpHVnlZWFJsY3lJc0ltbHpJam9pTVRjeE5EVXhNelF4T0RFNE9UTXlPREV4TnlKOSJ9fQ",
},
},
"",
},
{
http.StatusOK,
&Options{ClientID: "my-client-id", UserAccessToken: "moderatedchannels-access-token"},
&GetModeratedChannelsParams{UserID: "154315414", After: "eyJiIjpudWxsLCJhIjp7IkN1cnNvciI6ImV5SjBjQ0k2SW5WelpYSTZNVFUwTXpFMU5ERTBPbTF2WkdWeVlYUmxjeUlzSW5Seklqb2lZMmhoYm01bGJEb3hNVE01TkRRMU5qTWlMQ0pwY0NJNkluVnpaWEk2TVRVME16RTFOREUwT20xdlpHVnlZWFJsY3lJc0ltbHpJam9pTVRjeE5EVXhNelF4T0RFNE9UTXlPREV4TnlKOSJ9fQ"},
`{
"data": [
{
"broadcaster_id": "106590483",
"broadcaster_login": "vaiastol",
"broadcaster_name": "vaiastol"
}
],
"pagination": {}
}`,
&ManyModeratedChannels{
ModeratedChannels: []ModeratedChannel{
{
BroadcasterID: "106590483",
BroadcasterLogin: "vaiastol",
BroadcasterName: "vaiastol",
},
},
},
"",
},
{
http.StatusUnauthorized,
&Options{ClientID: "my-client-id", UserAccessToken: "invalid-access-token"},
&GetModeratedChannelsParams{UserID: "154315414"},
`{"error":"Unauthorized","status":401,"message":"Invalid OAuth token"}`,
&ManyModeratedChannels{},
"",
},
{
http.StatusUnauthorized,
&Options{ClientID: "my-client-id", UserAccessToken: "moderatedchannels-access-token"},
&GetModeratedChannelsParams{UserID: "123456789"},
`{"error":"Unauthorized","status":401,"message":"The ID in user_id must match the user ID found in the request's OAuth token."}`,
&ManyModeratedChannels{},
"",
},
{
http.StatusUnauthorized,
&Options{ClientID: "my-client-id", UserAccessToken: "missingscope-access-token"},
&GetModeratedChannelsParams{UserID: "154315414"},
`{"error":"Unauthorized","status":401,"message":"Missing scope: user:read:moderated_channels"}`,
&ManyModeratedChannels{},
"",
},
{
http.StatusBadRequest,
&Options{ClientID: "my-client-id", UserAccessToken: "moderatedchannels-access-token"},
&GetModeratedChannelsParams{},
`{"error":"Bad Request","status":400,"message":"Missing required parameter \"user_id\""}`,
&ManyModeratedChannels{},
"user id is required",
},
}

for _, testCase := range testCases {
c := newMockClient(testCase.options, newMockHandler(testCase.statusCode, testCase.respBody, nil))

resp, err := c.GetModeratedChannels(testCase.params)

if err != nil {
if err.Error() != testCase.errorMsg {
t.Errorf("expected error message to be %s, got %s", testCase.errorMsg, err.Error())
}
continue
}

if resp.StatusCode != testCase.statusCode {
t.Errorf("expected status code to be %d, got %d", testCase.statusCode, resp.StatusCode)
}

for i, channel := range resp.Data.ModeratedChannels {
if channel.BroadcasterID != testCase.parsed.ModeratedChannels[i].BroadcasterID {
t.Errorf("Expected ModeratedChannel field BroadcasterID = %s, was %s", testCase.parsed.ModeratedChannels[i].BroadcasterID, channel.BroadcasterID)
}

if channel.BroadcasterLogin != testCase.parsed.ModeratedChannels[i].BroadcasterLogin {
t.Errorf("Expected ModeratedChannel field BroadcasterLogin = %s, was %s", testCase.parsed.ModeratedChannels[i].BroadcasterLogin, channel.BroadcasterLogin)
}

if channel.BroadcasterName != testCase.parsed.ModeratedChannels[i].BroadcasterName {
t.Errorf("Expected ModeratedChannel field BroadcasterName = %s, was %s", testCase.parsed.ModeratedChannels[i].BroadcasterName, channel.BroadcasterName)
}
}

if resp.Data.Pagination.Cursor != testCase.parsed.Pagination.Cursor {
t.Errorf("Expected Pagination field Cursor = %s, was %s", testCase.parsed.Pagination.Cursor, resp.Data.Pagination.Cursor)
}

}

// Test with HTTP Failure
options := &Options{
ClientID: "my-client-id",
HTTPClient: &badMockHTTPClient{
newMockHandler(0, "", nil),
},
}
c := &Client{
opts: options,
ctx: context.Background(),
}

_, err := c.GetModeratedChannels(&GetModeratedChannelsParams{UserID: "154315414"})
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")
}
}

0 comments on commit 22863cc

Please sign in to comment.