2022-10-25 16:47:30 +00:00
|
|
|
package twitch
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2023-07-21 18:37:20 +00:00
|
|
|
"io"
|
2022-10-25 16:47:30 +00:00
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
2023-07-21 18:37:20 +00:00
|
|
|
const (
|
|
|
|
errMessageAlreadyBanned = "The user specified in the user_id field is already banned."
|
|
|
|
maxTimeoutDuration = 1209600 * time.Second
|
|
|
|
)
|
2022-10-25 16:47:30 +00:00
|
|
|
|
|
|
|
// BanUser bans or timeouts a user in the given channel. Setting the
|
|
|
|
// duration to 0 will result in a ban, setting if greater than 0 will
|
|
|
|
// result in a timeout. The timeout is automatically converted to
|
|
|
|
// full seconds. The timeout duration must be less than 1209600s.
|
2024-01-01 16:52:18 +00:00
|
|
|
func (c *Client) BanUser(ctx context.Context, channel, username string, duration time.Duration, reason string) error {
|
2022-10-25 16:47:30 +00:00
|
|
|
var payload struct {
|
|
|
|
Data struct {
|
|
|
|
Duration int64 `json:"duration,omitempty"`
|
|
|
|
Reason string `json:"reason"`
|
|
|
|
UserID string `json:"user_id"`
|
|
|
|
} `json:"data"`
|
|
|
|
}
|
|
|
|
|
|
|
|
if duration > maxTimeoutDuration {
|
|
|
|
return errors.New("timeout duration exceeds maximum")
|
|
|
|
}
|
|
|
|
|
|
|
|
payload.Data.Duration = int64(duration / time.Second)
|
|
|
|
payload.Data.Reason = reason
|
|
|
|
|
2024-01-01 16:52:18 +00:00
|
|
|
botID, _, err := c.GetAuthorizedUser(ctx)
|
2022-10-25 16:47:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "getting bot user-id")
|
|
|
|
}
|
|
|
|
|
2024-01-01 16:52:18 +00:00
|
|
|
channelID, err := c.GetIDForUsername(ctx, strings.TrimLeft(channel, "#@"))
|
2022-10-25 16:47:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "getting channel user-id")
|
|
|
|
}
|
|
|
|
|
2024-01-01 16:52:18 +00:00
|
|
|
if payload.Data.UserID, err = c.GetIDForUsername(ctx, username); err != nil {
|
2022-10-25 16:47:30 +00:00
|
|
|
return errors.Wrap(err, "getting target user-id")
|
|
|
|
}
|
|
|
|
|
|
|
|
body := new(bytes.Buffer)
|
|
|
|
if err = json.NewEncoder(body).Encode(payload); err != nil {
|
|
|
|
return errors.Wrap(err, "encoding payload")
|
|
|
|
}
|
|
|
|
|
|
|
|
return errors.Wrap(
|
2024-01-01 16:52:18 +00:00
|
|
|
c.Request(ctx, ClientRequestOpts{
|
2023-07-01 14:48:21 +00:00
|
|
|
AuthType: AuthTypeBearerToken,
|
2022-10-25 16:47:30 +00:00
|
|
|
Method: http.MethodPost,
|
|
|
|
OKStatus: http.StatusOK,
|
|
|
|
Body: body,
|
|
|
|
URL: fmt.Sprintf(
|
|
|
|
"https://api.twitch.tv/helix/moderation/bans?broadcaster_id=%s&moderator_id=%s",
|
|
|
|
channelID, botID,
|
|
|
|
),
|
2023-07-21 18:37:20 +00:00
|
|
|
ValidateFunc: func(opts ClientRequestOpts, resp *http.Response) error {
|
|
|
|
if resp.StatusCode == http.StatusBadRequest {
|
|
|
|
// The user might already be banned, lets check the error in detail
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
return newHTTPError(resp.StatusCode, nil, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var payload ErrorResponse
|
|
|
|
if err = json.Unmarshal(body, &payload); err == nil && payload.Message == errMessageAlreadyBanned {
|
|
|
|
// The user is already banned, that's fine as that was
|
|
|
|
// our goal!
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return newHTTPError(resp.StatusCode, body, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ValidateStatus(opts, resp)
|
|
|
|
},
|
2022-10-25 16:47:30 +00:00
|
|
|
}),
|
|
|
|
"executing ban request",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteMessage deletes one or all messages from the specified chat.
|
|
|
|
// If no messageID is given all messages are deleted. If a message ID
|
|
|
|
// is given the message must be no older than 6 hours and it must not
|
|
|
|
// be posted by broadcaster or moderator.
|
2024-01-01 16:52:18 +00:00
|
|
|
func (c *Client) DeleteMessage(ctx context.Context, channel, messageID string) error {
|
|
|
|
botID, _, err := c.GetAuthorizedUser(ctx)
|
2022-10-25 16:47:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "getting bot user-id")
|
|
|
|
}
|
|
|
|
|
2024-01-01 16:52:18 +00:00
|
|
|
channelID, err := c.GetIDForUsername(ctx, strings.TrimLeft(channel, "#@"))
|
2022-10-25 16:47:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "getting channel user-id")
|
|
|
|
}
|
|
|
|
|
|
|
|
params := make(url.Values)
|
|
|
|
params.Set("broadcaster_id", channelID)
|
|
|
|
params.Set("moderator_id", botID)
|
|
|
|
if messageID != "" {
|
|
|
|
params.Set("message_id", messageID)
|
|
|
|
}
|
|
|
|
|
|
|
|
return errors.Wrap(
|
2024-01-01 16:52:18 +00:00
|
|
|
c.Request(ctx, ClientRequestOpts{
|
2023-07-01 14:48:21 +00:00
|
|
|
AuthType: AuthTypeBearerToken,
|
2022-10-25 16:47:30 +00:00
|
|
|
Method: http.MethodDelete,
|
|
|
|
OKStatus: http.StatusNoContent,
|
|
|
|
URL: fmt.Sprintf(
|
|
|
|
"https://api.twitch.tv/helix/moderation/chat?%s",
|
|
|
|
params.Encode(),
|
|
|
|
),
|
|
|
|
}),
|
|
|
|
"executing delete request",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnbanUser removes a timeout or ban given to the user in the channel
|
2024-01-01 16:52:18 +00:00
|
|
|
func (c *Client) UnbanUser(ctx context.Context, channel, username string) error {
|
|
|
|
botID, _, err := c.GetAuthorizedUser(ctx)
|
2022-10-25 16:47:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "getting bot user-id")
|
|
|
|
}
|
|
|
|
|
2024-01-01 16:52:18 +00:00
|
|
|
channelID, err := c.GetIDForUsername(ctx, strings.TrimLeft(channel, "#@"))
|
2022-10-25 16:47:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "getting channel user-id")
|
|
|
|
}
|
|
|
|
|
2024-01-01 16:52:18 +00:00
|
|
|
userID, err := c.GetIDForUsername(ctx, username)
|
2022-10-25 16:47:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "getting target user-id")
|
|
|
|
}
|
|
|
|
|
|
|
|
return errors.Wrap(
|
2024-01-01 16:52:18 +00:00
|
|
|
c.Request(ctx, ClientRequestOpts{
|
2023-07-01 14:48:21 +00:00
|
|
|
AuthType: AuthTypeBearerToken,
|
2022-10-25 16:47:30 +00:00
|
|
|
Method: http.MethodDelete,
|
|
|
|
OKStatus: http.StatusNoContent,
|
|
|
|
URL: fmt.Sprintf(
|
|
|
|
"https://api.twitch.tv/helix/moderation/bans?broadcaster_id=%s&moderator_id=%s&user_id=%s",
|
|
|
|
channelID, botID, userID,
|
|
|
|
),
|
|
|
|
}),
|
|
|
|
"executing unban request",
|
|
|
|
)
|
|
|
|
}
|
2023-02-04 12:59:18 +00:00
|
|
|
|
|
|
|
// UpdateShieldMode activates or deactivates the Shield Mode in the given channel
|
|
|
|
func (c *Client) UpdateShieldMode(ctx context.Context, channel string, enable bool) error {
|
2024-01-01 16:52:18 +00:00
|
|
|
botID, _, err := c.GetAuthorizedUser(ctx)
|
2023-02-04 12:59:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "getting bot user-id")
|
|
|
|
}
|
|
|
|
|
2024-01-01 16:52:18 +00:00
|
|
|
channelID, err := c.GetIDForUsername(ctx, strings.TrimLeft(channel, "#@"))
|
2023-02-04 12:59:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "getting channel user-id")
|
|
|
|
}
|
|
|
|
|
|
|
|
body := new(bytes.Buffer)
|
|
|
|
if err = json.NewEncoder(body).Encode(map[string]bool{
|
|
|
|
"is_active": enable,
|
|
|
|
}); err != nil {
|
|
|
|
return errors.Wrap(err, "encoding payload")
|
|
|
|
}
|
|
|
|
|
|
|
|
return errors.Wrap(
|
2024-01-01 16:52:18 +00:00
|
|
|
c.Request(ctx, ClientRequestOpts{
|
2023-07-01 14:48:21 +00:00
|
|
|
AuthType: AuthTypeBearerToken,
|
2023-02-04 12:59:18 +00:00
|
|
|
Method: http.MethodPut,
|
|
|
|
OKStatus: http.StatusOK,
|
|
|
|
Body: body,
|
|
|
|
URL: fmt.Sprintf(
|
|
|
|
"https://api.twitch.tv/helix/moderation/shield_mode?broadcaster_id=%s&moderator_id=%s",
|
|
|
|
channelID, botID,
|
|
|
|
),
|
|
|
|
}),
|
|
|
|
"executing update request",
|
|
|
|
)
|
|
|
|
}
|