Repository: nicklaw5/helix
Pull request #225 (2024-07-04 13:01:48)

feat: warn chat user endpoint
- added docs for Warn Chat User / ```SendModeratorWarnMessage```
- added ```SendModeratorWarnMessage``` func
- added tests
- [Warn Chat User Doc](https://dev.twitch.tv/docs/api/reference/#warn-chat-user)


Patch:
diff --git a/SUPPORTED_ENDPOINTS.md b/SUPPORTED_ENDPOINTS.md
index 0c6235c..71b90dc 100644
--- a/SUPPORTED_ENDPOINTS.md
+++ b/SUPPORTED_ENDPOINTS.md
@@ -124,3 +124,4 @@
 - [x] Delete Videos
 - [x] Send Whisper
 - [x] Get Webhook Subscriptions
+- [x] Warn Chat User
diff --git a/docs/moderation_docs.md b/docs/moderation_docs.md
index d08a13c..6a29d21 100644
--- a/docs/moderation_docs.md
+++ b/docs/moderation_docs.md
@@ -314,3 +314,31 @@ if err != nil {
 
 fmt.Printf("%+v\n", resp)
 ```
+
+## Warn Chat User
+
+Requires a user access token that includes the moderator:manage:warnings scope. Query parameter moderator_id must match the user_id in the user access token.
+
+```go
+client, err := helix.NewClient(&helix.Options{
+    ClientID: "your-client-id",
+    UserAccessToken: "your-user-access-token",
+})
+if err != nil {
+    // handle error
+}
+
+resp, err := client.SendModeratorWarnMessage(
+    &SendModeratorWarnChatMessageParams{
+        BroadcasterID: "1234",
+        ModeratorID:   "5678",
+        UserID:        "9876",
+        Reason:        "Test warning message",
+    },
+)
+if err != nil {
+    // handle error
+}
+
+fmt.Printf("%+v\n", resp)
+```
\ No newline at end of file
diff --git a/moderation.go b/moderation.go
index 9d18806..d2c61f7 100644
--- a/moderation.go
+++ b/moderation.go
@@ -426,3 +426,59 @@ func (c *Client) GetModeratedChannels(params *GetModeratedChannelsParams) (*GetM
 
 	return moderatedChannels, nil
 }
+
+type SendModeratorWarnChatMessageParams struct {
+	// The ID of the broadcaster whose chat room the message will be sent to
+	BroadcasterID string `query:"broadcaster_id"`
+
+	// The ID of the twitch user who requested the warning.
+	ModeratorID string `query:"moderator_id"`
+
+	// The ID of the user sent the WARN message
+	UserID string `json:"user_id"`
+
+	// The warn message to send.
+	Reason string `json:"reason"`
+}
+
+type ModeratorWarnChatMessage struct {
+	BroadcasterID string `json:"broadcaster_id"`
+	ModeratorID   string `json:"moderator_id"`
+	UserID        string `json:"user_id"`
+	Reason        string `json:"reason"`
+}
+
+type ManyModeratorWarnChatMessages struct {
+	Warnings []ModeratorWarnChatMessage `json:"data"`
+}
+
+type SendModeratorWarnChatResponse struct {
+	ResponseCommon
+
+	Data ManyModeratorWarnChatMessages
+}
+
+// SendModeratorWarnMessage Sends a warning message to a user in the broadcaster’s chat.
+// Required moderator:manage:warnings
+func (c *Client) SendModeratorWarnMessage(params *SendModeratorWarnChatMessageParams) (*SendModeratorWarnChatResponse, error) {
+	if params.BroadcasterID == "" {
+		return nil, errors.New("error: broadcaster id must be specified")
+	}
+	if params.ModeratorID == "" {
+		return nil, errors.New("error: moderator id must be specified")
+	}
+	if params.UserID == "" {
+		return nil, errors.New("error: user id must be specified")
+	}
+
+	resp, err := c.postAsJSON("moderation/warnings", &ManyModeratorWarnChatMessages{}, params)
+	if err != nil {
+		return nil, err
+	}
+
+	messageResponse := &SendModeratorWarnChatResponse{}
+	resp.HydrateResponseCommon(&messageResponse.ResponseCommon)
+	messageResponse.Data.Warnings = resp.Data.(*ManyModeratorWarnChatMessages).Warnings
+
+	return messageResponse, nil
+}


Test patch:
diff --git a/moderation_test.go b/moderation_test.go
index b1742b0..7241dc8 100644
--- a/moderation_test.go
+++ b/moderation_test.go
@@ -1117,3 +1117,141 @@ func TestGetModeratedChannels(t *testing.T) {
 		t.Error("expected error does match return error")
 	}
 }
+
+func TestSendModeratorWarnMessage(t *testing.T) {
+	t.Parallel()
+
+	testCases := []struct {
+		statusCode int
+		options    *Options
+		params     *SendModeratorWarnChatMessageParams
+		respBody   string
+		errorMsg   string
+	}{
+		{
+			http.StatusOK,
+			&Options{ClientID: "my-client-id", UserAccessToken: "moderator-access-token"},
+			&SendModeratorWarnChatMessageParams{
+				BroadcasterID: "1234",
+				ModeratorID:   "5678",
+				UserID:        "9876",
+				Reason:        "Test warning message",
+			},
+			`{"data": [{"broadcaster_id": "1234", "moderator_id": "5678", "user_id": "9876", "reason": "Test warning message"}]}`,
+			"",
+		},
+		{
+			http.StatusOK,
+			&Options{ClientID: "my-client-id", UserAccessToken: "invalid-access-token"},
+			&SendModeratorWarnChatMessageParams{
+				BroadcasterID: "1234",
+				ModeratorID:   "5678",
+				Reason:        "Test warning message",
+			},
+			"",
+			"error: user id must be specified",
+		},
+		{
+			http.StatusOK,
+			&Options{ClientID: "my-client-id", UserAccessToken: "invalid-access-token"},
+			&SendModeratorWarnChatMessageParams{
+				UserID:      "1234",
+				ModeratorID: "5678",
+				Reason:      "Test warning message",
+			},
+			"",
+			"error: broadcaster id must be specified",
+		},
+		{
+			http.StatusOK,
+			&Options{ClientID: "my-client-id", UserAccessToken: "invalid-access-token"},
+			&SendModeratorWarnChatMessageParams{
+				UserID:        "1234",
+				BroadcasterID: "12345",
+				Reason:        "Test warning message",
+			},
+			"",
+			"error: moderator id must be specified",
+		},
+		{
+			http.StatusUnauthorized,
+			&Options{ClientID: "my-client-id", UserAccessToken: "invalid-access-token"},
+			&SendModeratorWarnChatMessageParams{
+				BroadcasterID: "1234",
+				ModeratorID:   "5678",
+				UserID:        "9876",
+				Reason:        "Test warning message",
+			},
+			`{"error":"Unauthorized","status":401,"message":"Invalid OAuth token"}`,
+			"",
+		},
+		// Add more test cases as needed
+	}
+
+	for _, testCase := range testCases {
+		c := newMockClient(testCase.options, newMockHandler(testCase.statusCode, testCase.respBody, nil))
+
+		resp, err := c.SendModeratorWarnMessage(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)
+		}
+
+		if resp.StatusCode > http.StatusOK {
+			continue
+		}
+
+		if len(resp.Data.Warnings) == 0 {
+			continue
+		}
+		warning := resp.Data.Warnings[0]
+
+		if warning.BroadcasterID != testCase.params.BroadcasterID {
+			t.Errorf("expected broadcaster id to be %s, got %s", testCase.params.BroadcasterID, warning.BroadcasterID)
+		}
+
+		if warning.ModeratorID != testCase.params.ModeratorID {
+			t.Errorf("expected moderator id to be %s, got %s", testCase.params.ModeratorID, warning.ModeratorID)
+		}
+
+		if warning.UserID != testCase.params.UserID {
+			t.Errorf("expected user id to be %s, got %s", testCase.params.UserID, warning.UserID)
+		}
+
+		if warning.Reason != testCase.params.Reason {
+			t.Errorf("expected reason to be %s, got %s", testCase.params.Reason, warning.Reason)
+		}
+	}
+
+	// Test with HTTP Failure
+	options := &Options{
+		ClientID: "my-client-id",
+		HTTPClient: &badMockHTTPClient{
+			newMockHandler(0, "", nil),
+		},
+	}
+	c := &Client{
+		opts: options,
+		ctx:  context.Background(),
+	}
+
+	_, err := c.SendModeratorWarnMessage(&SendModeratorWarnChatMessageParams{
+		BroadcasterID: "1234",
+		ModeratorID:   "5678",
+		UserID:        "9876",
+		Reason:        "Test warning message",
+	})
+	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")
+	}
+}