Skip to content

Commit 22863cc

Browse files
authored
Merge pull request #223 from dx9er/feat/getmoderatedchannels
feat: Implement Get Moderated Channels endpoint
2 parents e1f84cf + 7916a0c commit 22863cc

File tree

4 files changed

+241
-0
lines changed

4 files changed

+241
-0
lines changed

SUPPORTED_ENDPOINTS.md

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
- [x] Add Blocked Term
7878
- [x] Remove Blocked Term
7979
- [x] Delete Chat Messages
80+
- [x] Get Moderated Channels
8081
- [x] Get Moderators
8182
- [x] Add Channel Moderator
8283
- [x] Remove Channel Moderator

docs/moderation_docs.md

+26
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,29 @@ if err != nil {
288288

289289
fmt.Printf("%+v\n", resp)
290290
```
291+
292+
## Get Moderated Channels
293+
294+
To use this function you need a user access token with the `user:read:moderated_channels` scope.
295+
`UserID` is required and must match the user ID of the user access token.
296+
297+
This is an example of how to get channels the user has moderator privileges in.
298+
299+
```go
300+
client, err := helix.NewClient(&helix.Options{
301+
ClientID: "your-client-id",
302+
UserAccessToken: "your-user-access-token",
303+
})
304+
if err != nil {
305+
// handle error
306+
}
307+
308+
resp, err := client.GetModeratedChannels(&helix.GetModeratedChannelsParams{
309+
UserID: "154315414",
310+
})
311+
if err != nil {
312+
// handle error
313+
}
314+
315+
fmt.Printf("%+v\n", resp)
316+
```

moderation.go

+46
Original file line numberDiff line numberDiff line change
@@ -380,3 +380,49 @@ func (c *Client) RemoveChannelModerator(params *RemoveChannelModeratorParams) (*
380380

381381
return moderators, nil
382382
}
383+
384+
// `UserID` must match the user ID in the User-Access token
385+
type GetModeratedChannelsParams struct {
386+
// Required
387+
UserID string `query:"user_id"`
388+
389+
// Optional
390+
After string `query:"after"`
391+
First int `query:"first"`
392+
}
393+
394+
type ModeratedChannel struct {
395+
BroadcasterID string `json:"broadcaster_id"`
396+
BroadcasterLogin string `json:"broadcaster_login"`
397+
BroadcasterName string `json:"broadcaster_name"`
398+
}
399+
400+
type ManyModeratedChannels struct {
401+
ModeratedChannels []ModeratedChannel `json:"data"`
402+
Pagination Pagination `json:"pagination"`
403+
}
404+
405+
type GetModeratedChannelsResponse struct {
406+
ResponseCommon
407+
Data ManyModeratedChannels
408+
}
409+
410+
// GetModeratedChannels Gets a list of channels that the specified user has moderator privileges in.
411+
// Required scope: user:read:moderated_channels
412+
func (c *Client) GetModeratedChannels(params *GetModeratedChannelsParams) (*GetModeratedChannelsResponse, error) {
413+
if params.UserID == "" {
414+
return nil, errors.New("user id is required")
415+
}
416+
417+
resp, err := c.get("/moderation/channels", &ManyModeratedChannels{}, params)
418+
if err != nil {
419+
return nil, err
420+
}
421+
422+
moderatedChannels := &GetModeratedChannelsResponse{}
423+
resp.HydrateResponseCommon(&moderatedChannels.ResponseCommon)
424+
moderatedChannels.Data.ModeratedChannels = resp.Data.(*ManyModeratedChannels).ModeratedChannels
425+
moderatedChannels.Data.Pagination = resp.Data.(*ManyModeratedChannels).Pagination
426+
427+
return moderatedChannels, nil
428+
}

moderation_test.go

+168
Original file line numberDiff line numberDiff line change
@@ -949,3 +949,171 @@ func TestRemoveChannelModerator(t *testing.T) {
949949
t.Error("expected error does match return error")
950950
}
951951
}
952+
953+
func TestGetModeratedChannels(t *testing.T) {
954+
t.Parallel()
955+
956+
testCases := []struct {
957+
statusCode int
958+
options *Options
959+
params *GetModeratedChannelsParams
960+
respBody string
961+
parsed *ManyModeratedChannels
962+
errorMsg string
963+
}{
964+
{
965+
http.StatusOK,
966+
&Options{ClientID: "my-client-id", UserAccessToken: "moderatedchannels-access-token"},
967+
&GetModeratedChannelsParams{UserID: "154315414", First: 2},
968+
`{
969+
"data": [
970+
{
971+
"broadcaster_id": "183094685",
972+
"broadcaster_login": "spaceashes",
973+
"broadcaster_name": "spaceashes"
974+
},
975+
{
976+
"broadcaster_id": "113944563",
977+
"broadcaster_login": "reapex_1",
978+
"broadcaster_name": "Reapex_1"
979+
}
980+
],
981+
"pagination": {
982+
"cursor": "eyJiIjpudWxsLCJhIjp7IkN1cnNvciI6ImV5SjBjQ0k2SW5WelpYSTZNVFUwTXpFMU5ERTBPbTF2WkdWeVlYUmxjeUlzSW5Seklqb2lZMmhoYm01bGJEb3hNVE01TkRRMU5qTWlMQ0pwY0NJNkluVnpaWEk2TVRVME16RTFOREUwT20xdlpHVnlZWFJsY3lJc0ltbHpJam9pTVRjeE5EVXhNelF4T0RFNE9UTXlPREV4TnlKOSJ9fQ"
983+
}
984+
}`,
985+
&ManyModeratedChannels{
986+
ModeratedChannels: []ModeratedChannel{
987+
{
988+
BroadcasterID: "183094685",
989+
BroadcasterLogin: "spaceashes",
990+
BroadcasterName: "spaceashes",
991+
},
992+
{
993+
BroadcasterID: "113944563",
994+
BroadcasterLogin: "reapex_1",
995+
BroadcasterName: "Reapex_1",
996+
},
997+
},
998+
Pagination: Pagination{
999+
Cursor: "eyJiIjpudWxsLCJhIjp7IkN1cnNvciI6ImV5SjBjQ0k2SW5WelpYSTZNVFUwTXpFMU5ERTBPbTF2WkdWeVlYUmxjeUlzSW5Seklqb2lZMmhoYm01bGJEb3hNVE01TkRRMU5qTWlMQ0pwY0NJNkluVnpaWEk2TVRVME16RTFOREUwT20xdlpHVnlZWFJsY3lJc0ltbHpJam9pTVRjeE5EVXhNelF4T0RFNE9UTXlPREV4TnlKOSJ9fQ",
1000+
},
1001+
},
1002+
"",
1003+
},
1004+
{
1005+
http.StatusOK,
1006+
&Options{ClientID: "my-client-id", UserAccessToken: "moderatedchannels-access-token"},
1007+
&GetModeratedChannelsParams{UserID: "154315414", After: "eyJiIjpudWxsLCJhIjp7IkN1cnNvciI6ImV5SjBjQ0k2SW5WelpYSTZNVFUwTXpFMU5ERTBPbTF2WkdWeVlYUmxjeUlzSW5Seklqb2lZMmhoYm01bGJEb3hNVE01TkRRMU5qTWlMQ0pwY0NJNkluVnpaWEk2TVRVME16RTFOREUwT20xdlpHVnlZWFJsY3lJc0ltbHpJam9pTVRjeE5EVXhNelF4T0RFNE9UTXlPREV4TnlKOSJ9fQ"},
1008+
`{
1009+
"data": [
1010+
{
1011+
"broadcaster_id": "106590483",
1012+
"broadcaster_login": "vaiastol",
1013+
"broadcaster_name": "vaiastol"
1014+
}
1015+
],
1016+
"pagination": {}
1017+
}`,
1018+
&ManyModeratedChannels{
1019+
ModeratedChannels: []ModeratedChannel{
1020+
{
1021+
BroadcasterID: "106590483",
1022+
BroadcasterLogin: "vaiastol",
1023+
BroadcasterName: "vaiastol",
1024+
},
1025+
},
1026+
},
1027+
"",
1028+
},
1029+
{
1030+
http.StatusUnauthorized,
1031+
&Options{ClientID: "my-client-id", UserAccessToken: "invalid-access-token"},
1032+
&GetModeratedChannelsParams{UserID: "154315414"},
1033+
`{"error":"Unauthorized","status":401,"message":"Invalid OAuth token"}`,
1034+
&ManyModeratedChannels{},
1035+
"",
1036+
},
1037+
{
1038+
http.StatusUnauthorized,
1039+
&Options{ClientID: "my-client-id", UserAccessToken: "moderatedchannels-access-token"},
1040+
&GetModeratedChannelsParams{UserID: "123456789"},
1041+
`{"error":"Unauthorized","status":401,"message":"The ID in user_id must match the user ID found in the request's OAuth token."}`,
1042+
&ManyModeratedChannels{},
1043+
"",
1044+
},
1045+
{
1046+
http.StatusUnauthorized,
1047+
&Options{ClientID: "my-client-id", UserAccessToken: "missingscope-access-token"},
1048+
&GetModeratedChannelsParams{UserID: "154315414"},
1049+
`{"error":"Unauthorized","status":401,"message":"Missing scope: user:read:moderated_channels"}`,
1050+
&ManyModeratedChannels{},
1051+
"",
1052+
},
1053+
{
1054+
http.StatusBadRequest,
1055+
&Options{ClientID: "my-client-id", UserAccessToken: "moderatedchannels-access-token"},
1056+
&GetModeratedChannelsParams{},
1057+
`{"error":"Bad Request","status":400,"message":"Missing required parameter \"user_id\""}`,
1058+
&ManyModeratedChannels{},
1059+
"user id is required",
1060+
},
1061+
}
1062+
1063+
for _, testCase := range testCases {
1064+
c := newMockClient(testCase.options, newMockHandler(testCase.statusCode, testCase.respBody, nil))
1065+
1066+
resp, err := c.GetModeratedChannels(testCase.params)
1067+
1068+
if err != nil {
1069+
if err.Error() != testCase.errorMsg {
1070+
t.Errorf("expected error message to be %s, got %s", testCase.errorMsg, err.Error())
1071+
}
1072+
continue
1073+
}
1074+
1075+
if resp.StatusCode != testCase.statusCode {
1076+
t.Errorf("expected status code to be %d, got %d", testCase.statusCode, resp.StatusCode)
1077+
}
1078+
1079+
for i, channel := range resp.Data.ModeratedChannels {
1080+
if channel.BroadcasterID != testCase.parsed.ModeratedChannels[i].BroadcasterID {
1081+
t.Errorf("Expected ModeratedChannel field BroadcasterID = %s, was %s", testCase.parsed.ModeratedChannels[i].BroadcasterID, channel.BroadcasterID)
1082+
}
1083+
1084+
if channel.BroadcasterLogin != testCase.parsed.ModeratedChannels[i].BroadcasterLogin {
1085+
t.Errorf("Expected ModeratedChannel field BroadcasterLogin = %s, was %s", testCase.parsed.ModeratedChannels[i].BroadcasterLogin, channel.BroadcasterLogin)
1086+
}
1087+
1088+
if channel.BroadcasterName != testCase.parsed.ModeratedChannels[i].BroadcasterName {
1089+
t.Errorf("Expected ModeratedChannel field BroadcasterName = %s, was %s", testCase.parsed.ModeratedChannels[i].BroadcasterName, channel.BroadcasterName)
1090+
}
1091+
}
1092+
1093+
if resp.Data.Pagination.Cursor != testCase.parsed.Pagination.Cursor {
1094+
t.Errorf("Expected Pagination field Cursor = %s, was %s", testCase.parsed.Pagination.Cursor, resp.Data.Pagination.Cursor)
1095+
}
1096+
1097+
}
1098+
1099+
// Test with HTTP Failure
1100+
options := &Options{
1101+
ClientID: "my-client-id",
1102+
HTTPClient: &badMockHTTPClient{
1103+
newMockHandler(0, "", nil),
1104+
},
1105+
}
1106+
c := &Client{
1107+
opts: options,
1108+
ctx: context.Background(),
1109+
}
1110+
1111+
_, err := c.GetModeratedChannels(&GetModeratedChannelsParams{UserID: "154315414"})
1112+
if err == nil {
1113+
t.Error("expected error but got nil")
1114+
}
1115+
1116+
if err.Error() != "Failed to execute API request: Oops, that's bad :(" {
1117+
t.Error("expected error does match return error")
1118+
}
1119+
}

0 commit comments

Comments
 (0)