From e941e414e277b71d464cce4bd52a2e8d741b62ed Mon Sep 17 00:00:00 2001 From: Evan Fiordeliso Date: Sun, 3 Mar 2024 15:14:59 -0500 Subject: [PATCH] Add Moderation endpoints to API --- api/api.go | 3 + api/moderation/add_blocked_term.go | 74 ++++++++++++ api/moderation/add_channel_moderator.go | 40 +++++++ api/moderation/add_channel_vip.go | 40 +++++++ api/moderation/ban_user.go | 101 ++++++++++++++++ api/moderation/check_automod_status.go | 73 ++++++++++++ api/moderation/delete_chat_messages.go | 50 ++++++++ api/moderation/get_automod_settings.go | 51 ++++++++ api/moderation/get_banned_users.go | 104 +++++++++++++++++ api/moderation/get_blocked_terms.go | 65 +++++++++++ api/moderation/get_moderated_channels.go | 72 ++++++++++++ api/moderation/get_moderators.go | 82 +++++++++++++ api/moderation/get_shield_mode_status.go | 52 +++++++++ api/moderation/get_vips.go | 79 +++++++++++++ .../manage_held_automod_messages.go | 49 ++++++++ api/moderation/models.go | 85 ++++++++++++++ api/moderation/moderation.go | 18 +++ api/moderation/remove_blocked_term.go | 42 +++++++ api/moderation/remove_channel_moderator.go | 40 +++++++ api/moderation/remove_channel_vip.go | 43 +++++++ api/moderation/unban_user.go | 44 +++++++ api/moderation/update_automod_settings.go | 110 ++++++++++++++++++ api/moderation/update_shield_mode_status.go | 69 +++++++++++ 23 files changed, 1386 insertions(+) create mode 100644 api/moderation/add_blocked_term.go create mode 100644 api/moderation/add_channel_moderator.go create mode 100644 api/moderation/add_channel_vip.go create mode 100644 api/moderation/ban_user.go create mode 100644 api/moderation/check_automod_status.go create mode 100644 api/moderation/delete_chat_messages.go create mode 100644 api/moderation/get_automod_settings.go create mode 100644 api/moderation/get_banned_users.go create mode 100644 api/moderation/get_blocked_terms.go create mode 100644 api/moderation/get_moderated_channels.go create mode 100644 api/moderation/get_moderators.go create mode 100644 api/moderation/get_shield_mode_status.go create mode 100644 api/moderation/get_vips.go create mode 100644 api/moderation/manage_held_automod_messages.go create mode 100644 api/moderation/models.go create mode 100644 api/moderation/moderation.go create mode 100644 api/moderation/remove_blocked_term.go create mode 100644 api/moderation/remove_channel_moderator.go create mode 100644 api/moderation/remove_channel_vip.go create mode 100644 api/moderation/unban_user.go create mode 100644 api/moderation/update_automod_settings.go create mode 100644 api/moderation/update_shield_mode_status.go diff --git a/api/api.go b/api/api.go index 8358ade..e73d429 100644 --- a/api/api.go +++ b/api/api.go @@ -20,6 +20,7 @@ import ( "go.fifitido.net/twitch/api/goals" "go.fifitido.net/twitch/api/gueststar" "go.fifitido.net/twitch/api/hypetrain" + "go.fifitido.net/twitch/api/moderation" ) const HelixBaseUrl = "https://api.twitch.tv/helix" @@ -44,6 +45,7 @@ type API struct { Goals *goals.Goals GuestStar *gueststar.GuestStar Hypetrain *hypetrain.Hypetrain + Moderation *moderation.Moderation } func New() *API { @@ -70,5 +72,6 @@ func New() *API { Goals: goals.New(client, baseUrl), GuestStar: gueststar.New(client, baseUrl), Hypetrain: hypetrain.New(client, baseUrl), + Moderation: moderation.New(client, baseUrl), } } diff --git a/api/moderation/add_blocked_term.go b/api/moderation/add_blocked_term.go new file mode 100644 index 0000000..38627d2 --- /dev/null +++ b/api/moderation/add_blocked_term.go @@ -0,0 +1,74 @@ +package moderation + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" +) + +type AddBlockedTermParams struct { + // The ID of the broadcaster that owns the list of blocked terms. + BroadcasterID string `url:"broadcaster_id"` + + // The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. + // This ID must match the user ID in the user access token. + ModeratorID string `url:"moderator_id"` +} + +type AddBlockedTermRequest struct { + // The word or phrase to block from being used in the broadcaster’s chat room. + // The term must contain a minimum of 2 characters and may contain up to a maximum of 500 characters. + // + // Terms may include a wildcard character (*). + // The wildcard character must appear at the beginning or end of a word or set of characters. + // For example, *foo or foo*. + // + // If the blocked term already exists, the response contains the existing blocked term. + Text string `json:"text"` +} + +type AddBlockedTermResponse struct { + // A list that contains the single blocked term that the broadcaster added. + Data []BlockedTerm `json:"data"` +} + +// Adds a word or phrase to the broadcaster’s list of blocked terms. +// These are the terms that the broadcaster doesn’t want used in their chat room. +// +// Requires a user access token that includes the moderator:manage:blocked_terms scope. +func (m *Moderation) AddBlockedTerm(ctx context.Context, params *AddBlockedTermParams, body *AddBlockedTermRequest) (*AddBlockedTermResponse, error) { + v, _ := query.Values(params) + endpoint := m.baseUrl.ResolveReference(&url.URL{Path: "moderation/blocked_terms", RawQuery: v.Encode()}) + + r, w := io.Pipe() + + go func() { + if err := json.NewEncoder(w).Encode(body); err != nil { + w.CloseWithError(err) + } else { + w.Close() + } + }() + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), r) + if err != nil { + return nil, err + } + + res, err := m.client.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + var data AddBlockedTermResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/api/moderation/add_channel_moderator.go b/api/moderation/add_channel_moderator.go new file mode 100644 index 0000000..f517afd --- /dev/null +++ b/api/moderation/add_channel_moderator.go @@ -0,0 +1,40 @@ +package moderation + +import ( + "context" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" +) + +type AddChannelModeratorParams struct { + // The ID of the broadcaster that owns the chat room. This ID must match the user ID in the access token. + BroadcasterID string `url:"broadcaster_id"` + + // The ID of the user to add as a moderator in the broadcaster’s chat room. + UserID string `url:"user_id"` +} + +// Adds a moderator to the broadcaster’s chat room. +// +// Rate Limits: The broadcaster may add a maximum of 10 moderators within a 10-second window. +// +// Requires a user access token that includes the channel:manage:moderators scope. +func (m *Moderation) AddChannelModerator(ctx context.Context, params *AddChannelModeratorParams) error { + v, _ := query.Values(params) + endpoint := m.baseUrl.ResolveReference(&url.URL{Path: "moderation/moderators", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), nil) + if err != nil { + return err + } + + res, err := m.client.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + return nil +} diff --git a/api/moderation/add_channel_vip.go b/api/moderation/add_channel_vip.go new file mode 100644 index 0000000..c88a14e --- /dev/null +++ b/api/moderation/add_channel_vip.go @@ -0,0 +1,40 @@ +package moderation + +import ( + "context" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" +) + +type AddChannelVIPParams struct { + // The ID of the user to give VIP status to. + UserID string `url:"user_id"` + + // The ID of the broadcaster that’s adding the user as a VIP. This ID must match the user ID in the access token. + BroadcasterID string `url:"broadcaster_id"` +} + +// Adds the specified user as a VIP in the broadcaster’s channel. +// +// Rate Limits: The broadcaster may add a maximum of 10 VIPs within a 10-second window. +// +// Requires a user access token that includes the channel:manage:vips scope. +func (m *Moderation) AddChannelVIP(ctx context.Context, params *AddChannelVIPParams) error { + v, _ := query.Values(params) + endpoint := m.baseUrl.ResolveReference(&url.URL{Path: "channels/vips", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), nil) + if err != nil { + return err + } + + res, err := m.client.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + return nil +} diff --git a/api/moderation/ban_user.go b/api/moderation/ban_user.go new file mode 100644 index 0000000..21619ed --- /dev/null +++ b/api/moderation/ban_user.go @@ -0,0 +1,101 @@ +package moderation + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/url" + "time" + + "github.com/google/go-querystring/query" +) + +type BanUserParams struct { + // The ID of the broadcaster whose chat room the user is being banned from. + BroadcasterID string `url:"broadcaster_id"` + + // The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. + // This ID must match the user ID in the user access token. + ModeratorID string `url:"moderator_id"` +} + +type BanUserRequest struct { + // The ID of the user to ban or put in a timeout. + UserID string `json:"user_id"` + + // To ban a user indefinitely, don’t include this field. + // + // To put a user in a timeout, include this field and specify the timeout period, in seconds. + // The minimum timeout is 1 second and the maximum is 1,209,600 seconds (2 weeks). + Duration *int `json:"duration"` + + // The reason the you’re banning the user or putting them in a timeout. + // The text is user defined and is limited to a maximum of 500 characters. + Reason *string `json:"reason"` +} + +type BanUserResponse struct { + // Identifies the user and type of ban. + Data []BanUserResponseData `json:"data"` +} + +type BanUserResponseData struct { + // The broadcaster whose chat room the user was banned from chatting in. + BroadcasterID string `json:"broadcaster_id"` + + // The moderator that banned or put the user in the timeout. + ModeratorID string `json:"moderator_id"` + + // The user that was banned or put in a timeout. + UserID string `json:"user_id"` + + // The UTC date and time (in RFC3339 format) that the ban or timeout was placed. + CreatedAt time.Time `json:"created_at"` + + // The UTC date and time (in RFC3339 format) that the timeout will end. Is null if the user was banned instead of being put in a timeout. + EndTime *time.Time `json:"end_time"` +} + +// Bans a user from participating in the specified broadcaster’s chat room or puts them in a timeout. +// +// For information about banning or putting users in a timeout, see Ban a User and Timeout a User. +// +// If the user is currently in a timeout, you can call this endpoint to change the duration of the timeout or ban them altogether. +// If the user is currently banned, you cannot call this method to put them in a timeout instead. +// +// To remove a ban or end a timeout, see Unban user. +// +// Requires a user access token that includes the moderator:manage:banned_users scope. +func (m *Moderation) BanUser(ctx context.Context, params *BanUserParams, body *BanUserRequest) (*BanUserResponse, error) { + v, _ := query.Values(params) + endpoint := m.baseUrl.ResolveReference(&url.URL{Path: "moderation/bans", RawQuery: v.Encode()}) + + r, w := io.Pipe() + + go func() { + if err := json.NewEncoder(w).Encode(body); err != nil { + w.CloseWithError(err) + } else { + w.Close() + } + }() + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), r) + if err != nil { + return nil, err + } + + res, err := m.client.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + var data BanUserResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/api/moderation/check_automod_status.go b/api/moderation/check_automod_status.go new file mode 100644 index 0000000..2ff127c --- /dev/null +++ b/api/moderation/check_automod_status.go @@ -0,0 +1,73 @@ +package moderation + +import ( + "context" + "encoding/json" + "net/http" + "net/url" +) + +type CheckAutoModStatusRequest struct { + Data []CheckAutoModStatusRequestData `json:"data"` +} + +type CheckAutoModStatusRequestData struct { + // A caller-defined ID used to correlate this message with the same message in the response. + MsgID string `json:"msg_id"` + + // The message to check. + MsgText string `json:"msg_text"` +} + +type CheckAutoModStatusResponse struct { + // The list of messages and whether Twitch would approve them for chat. + Data []CheckAutoModStatusResponseData `json:"data"` +} + +type CheckAutoModStatusResponseData struct { + // The caller-defined ID passed in the request. + MsgID string `json:"msg_id"` + + // A Boolean value that indicates whether Twitch would approve the message for chat or hold it for moderator review or block it from chat. + // Is true if Twitch would approve the message; otherwise, false if Twitch would hold the message for moderator review or block it from chat. + IsPermitted bool `json:"is_permitted"` +} + +// Checks whether AutoMod would flag the specified message for review. +// +// AutoMod is a moderation tool that holds inappropriate or harassing chat messages for moderators to review. Moderators approve or deny the messages that AutoMod flags; only approved messages are released to chat. AutoMod detects misspellings and evasive language automatically. For information about AutoMod, see How to Use AutoMod. +// +// Rate Limits: Rates are limited per channel based on the account type rather than per access token. +// +// Account type | Limit per minute | Limit per hour +// ------------ | ---------------- | -------------- +// Free | 1 | 10 +// Normal | 5 | 50 +// Affiliated | 10 | 100 +// Partnered | 30 | 300 +// +// The above limits are in addition to the standard Twitch API rate limits. +// The rate limit headers in the response represent the Twitch rate limits and not the above limits. +// +// Requires a user access token that includes the moderation:read scope. +func (c *Moderation) CheckAutoModStatus(ctx context.Context, broadcasterID string, params *CheckAutoModStatusRequest) (*CheckAutoModStatusResponse, error) { + endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "modetation/enforcements/status", RawQuery: url.Values{"broadcaster_id": {broadcasterID}}.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), nil) + if err != nil { + return nil, err + } + + res, err := c.client.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + var data CheckAutoModStatusResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/api/moderation/delete_chat_messages.go b/api/moderation/delete_chat_messages.go new file mode 100644 index 0000000..3a1f565 --- /dev/null +++ b/api/moderation/delete_chat_messages.go @@ -0,0 +1,50 @@ +package moderation + +import ( + "context" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" +) + +type DeleteChatMessagesParams struct { + // The ID of the broadcaster that owns the chat room to remove messages from. + BroadcasterID string `url:"broadcaster_id"` + + // The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. + // This ID must match the user ID in the user access token. + ModeratorID string `url:"moderator_id"` + + // The ID of the message to remove. The id tag in the PRIVMSG tag contains the message’s ID. Restrictions: + // + // - The message must have been created within the last 6 hours. + // + // - The message must not belong to the broadcaster. + // + // - The message must not belong to another moderator. + // + // If not specified, the request removes all messages in the broadcaster’s chat room. + MessageID *string `url:"message_id,omitempty"` +} + +// Removes a single chat message or all chat messages from the broadcaster’s chat room. +// +// Requires a user access token that includes the moderator:manage:chat_messages scope. +func (m *Moderation) DeleteChatMessages(ctx context.Context, params *DeleteChatMessagesParams) error { + v, _ := query.Values(params) + endpoint := m.baseUrl.ResolveReference(&url.URL{Path: "moderation/chat", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, endpoint.String(), nil) + if err != nil { + return err + } + + res, err := m.client.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + return nil +} diff --git a/api/moderation/get_automod_settings.go b/api/moderation/get_automod_settings.go new file mode 100644 index 0000000..4758597 --- /dev/null +++ b/api/moderation/get_automod_settings.go @@ -0,0 +1,51 @@ +package moderation + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" +) + +type GetAutoModSettingsParams struct { + // The ID of the broadcaster whose AutoMod settings you want to get. + BroadcasterID string `url:"broadcaster_id"` + + // The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. + // This ID must match the user ID in the user access token. + ModeratorID string `url:"moderator_id"` +} + +type GetAutoModSettingsResponse struct { + // The list of AutoMod settings. The list contains a single object that contains all the AutoMod settings. + Data []AutoModSettings `json:"data"` +} + +// Gets the broadcaster’s AutoMod settings. +// The settings are used to automatically block inappropriate or harassing messages from appearing in the broadcaster’s chat room. +// +// Requires a user access token that includes the moderator:read:automod_settings scope. +func (m *Moderation) GetAutoModSettings(ctx context.Context, params *GetAutoModSettingsParams) (*GetAutoModSettingsResponse, error) { + v, _ := query.Values(params) + endpoint := m.baseUrl.ResolveReference(&url.URL{Path: "moderation/automod/settings", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) + if err != nil { + return nil, err + } + + res, err := m.client.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + var data GetAutoModSettingsResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/api/moderation/get_banned_users.go b/api/moderation/get_banned_users.go new file mode 100644 index 0000000..90475cf --- /dev/null +++ b/api/moderation/get_banned_users.go @@ -0,0 +1,104 @@ +package moderation + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + "time" + + "github.com/google/go-querystring/query" + "go.fifitido.net/twitch/api/types" +) + +type GetBannedUsersParams struct { + // The ID of the broadcaster whose list of banned users you want to get. This ID must match the user ID in the access token. + BroadcasterID string `url:"broadcaster_id"` + + // A list of user IDs used to filter the results. + // To specify more than one ID, include this parameter for each user you want to get. + // For example, user_id=1234&user_id=5678. + // You may specify a maximum of 100 IDs. + // + // The returned list includes only those users that were banned or put in a timeout. + // The list is returned in the same order that you specified the IDs. + UserID []string `url:"user_id"` + + // The maximum number of items to return per page in the response. + // The minimum page size is 1 item per page and the maximum is 100 items per page. + // The default is 20. + First *int `url:"first"` + + // The cursor used to get the next page of results. The Pagination object in the response contains the cursor’s value. + // Read More: https://dev.twitch.tv/docs/api/guide#pagination + After *types.Cursor `url:"after"` + + // The cursor used to get the previous page of results. The Pagination object in the response contains the cursor’s value. + // Read More: https://dev.twitch.tv/docs/api/guide#pagination + Before *types.Cursor `url:"before"` +} + +type GetBannedUsersResponse struct { + // The list of banned users. The list contains a single object that contains all the banned users. + Data []GetBannedUsersResponseData `json:"data"` + + // Contains information about the pagination in the response. + // The object is empty if there are no more pages of results. + // Read More: https://dev.twitch.tv/docs/api/guide#pagination + Pagination types.Pagination `json:"pagination"` +} + +type GetBannedUsersResponseData struct { + // The ID of the banned user. + UserID string `json:"user_id"` + + // The banned user’s login name. + UserLogin string `json:"user_login"` + + // The banned user’s display name. + UserName string `json:"user_name"` + + // The UTC date and time (in RFC3339 format) of when the timeout expires, or an empty string if the user is permanently banned. + ExpiresAt time.Time `json:"expires_at"` + + // The UTC date and time (in RFC3339 format) of when the user was banned. + CreatedAt time.Time `json:"created_at"` + + // The reason the user was banned or put in a timeout if the moderator provided one. + Reason string `json:"reason"` + + // The ID of the moderator that banned the user or put them in a timeout. + ModeratorID string `json:"moderator_id"` + + // The moderator’s login name. + ModeratorLogin string `json:"moderator_login"` + + // The moderator’s display name. + ModeratorName string `json:"moderator_name"` +} + +// Gets all users that the broadcaster banned or put in a timeout. +// +// Requires a user access token that includes the moderation:read or moderator:manage:banned_users scope. +func (m *Moderation) GetBannedUsers(ctx context.Context, params *GetBannedUsersParams) (*GetBannedUsersResponse, error) { + v, _ := query.Values(params) + endpoint := m.baseUrl.ResolveReference(&url.URL{Path: "moderation/banned", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) + if err != nil { + return nil, err + } + + res, err := m.client.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + var data GetBannedUsersResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/api/moderation/get_blocked_terms.go b/api/moderation/get_blocked_terms.go new file mode 100644 index 0000000..fbcb0ec --- /dev/null +++ b/api/moderation/get_blocked_terms.go @@ -0,0 +1,65 @@ +package moderation + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" + "go.fifitido.net/twitch/api/types" +) + +type GetBlockedTermsParams struct { + // The ID of the broadcaster whose blocked terms you're getting. + BroadcasterID string `url:"broadcaster_id"` + + // The ID of the broadcaster or a user that has permission to moderate the broadcaster's chat room. + // This ID must match the user ID in the user access token. + ModeratorID string `url:"moderator_id"` + + // The maximum number of items to return per page in the response. + // The minimum page size is 1 item per page and the maximum is 100 items per page. + // The default is 20. + First *int `url:"first"` + + // The cursor used to get the next page of results. The Pagination object in the response contains the cursor’s value. + After *types.Cursor `url:"after"` +} + +type GetBlockedTermsResponse struct { + // The list of blocked terms. The list is in descending order of when they were created (see the created_at timestamp). + Data []BlockedTerm `json:"data"` + + // Contains information about the pagination in the response. + // The object is empty if there are no more pages of results. + // Read More: https://dev.twitch.tv/docs/api/guide#pagination + Pagination types.Pagination `json:"pagination"` +} + +// Gets the broadcaster’s list of non-private, blocked words or phrases. +// These are the terms that the broadcaster or moderator added manually or that were denied by AutoMod. +// +// Requires a user access token that includes the moderator:read:blocked_terms or moderator:manage:blocked_terms scope. +func (m *Moderation) GetBlockedTerms(ctx context.Context, params *GetBlockedTermsParams) (*GetBlockedTermsResponse, error) { + v, _ := query.Values(params) + endpoint := m.baseUrl.ResolveReference(&url.URL{Path: "moderation/blocked_terms", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) + if err != nil { + return nil, err + } + + res, err := m.client.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + var data GetBlockedTermsResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/api/moderation/get_moderated_channels.go b/api/moderation/get_moderated_channels.go new file mode 100644 index 0000000..3bf0e97 --- /dev/null +++ b/api/moderation/get_moderated_channels.go @@ -0,0 +1,72 @@ +package moderation + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" + "go.fifitido.net/twitch/api/types" +) + +type GetModeratedChannelsParams struct { + // A user’s ID. Returns the list of channels that this user has moderator privileges in. This ID must match the user ID in the user OAuth token + UserID string `url:"user_id"` + + // The cursor used to get the next page of results. The Pagination object in the response contains the cursor’s value. + After *types.Cursor `url:"after"` + + // The maximum number of items to return per page in the response. + // The minimum page size is 1 item per page and the maximum is 100 items per page. + // The default is 20. + First *int `url:"first"` +} + +type GetModeratedChannelsResponse struct { + // The list of channels that the user has moderator privileges in. + Data []GetModeratedChannelsResponseData `json:"data"` + + // Contains information about the pagination in the response. + // The object is empty if there are no more pages of results. + // Read More: https://dev.twitch.tv/docs/api/guide#pagination + Pagination types.Pagination `json:"pagination"` +} + +type GetModeratedChannelsResponseData struct { + // An ID that uniquely identifies the channel this user can moderate. + BroadcasterID string `json:"broadcaster_id"` + + // The channel’s login name. + BroadcasterLogin string `json:"broadcaster_login"` + + // The channels’ display name. + BroadcasterName string `json:"broadcaster_name"` +} + +// Gets a list of channels that the specified user has moderator privileges in. +// +// Query parameter user_id must match the user ID in the User-Access token +// Requires OAuth Scope: user:read:moderated_channels +func (m *Moderation) GetModeratedChannels(ctx context.Context, params *GetModeratedChannelsParams) (*GetModeratedChannelsResponse, error) { + v, _ := query.Values(params) + endpoint := m.baseUrl.ResolveReference(&url.URL{Path: "moderation/channels", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) + if err != nil { + return nil, err + } + + res, err := m.client.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + var data GetModeratedChannelsResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/api/moderation/get_moderators.go b/api/moderation/get_moderators.go new file mode 100644 index 0000000..5b24111 --- /dev/null +++ b/api/moderation/get_moderators.go @@ -0,0 +1,82 @@ +package moderation + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" + "go.fifitido.net/twitch/api/types" +) + +type GetModeratorsParams struct { + // The ID of the broadcaster whose list of moderators you want to get. This ID must match the user ID in the access token. + BroadcasterID string `url:"broadcaster_id"` + + // A list of user IDs used to filter the results. + // To specify more than one ID, include this parameter for each moderator you want to get. + // For example, user_id=1234&user_id=5678. + // You may specify a maximum of 100 IDs. + // + // The returned list includes only the users from the list who are moderators in the broadcaster’s channel. + // The list is returned in the same order as you specified the IDs. + UserID []string `url:"user_id"` + + // The maximum number of items to return per page in the response. + // The minimum page size is 1 item per page and the maximum is 100 items per page. + // The default is 20. + First *int `url:"first"` + + // The cursor used to get the next page of results. The Pagination object in the response contains the cursor’s value. + // Read More: https://dev.twitch.tv/docs/api/guide#pagination + After *types.Cursor `url:"after"` +} + +type GetModeratorsResponse struct { + // The list of moderators. The list contains a single object that contains all the moderators. + Data []GetModeratorsResponseData `json:"data"` + + // Contains information about the pagination in the response. + // The object is empty if there are no more pages of results. + // Read More: https://dev.twitch.tv/docs/api/guide#pagination + Pagination types.Pagination `json:"pagination"` +} + +type GetModeratorsResponseData struct { + // The ID of the user that has permission to moderate the broadcaster’s channel. + UserID string `json:"user_id"` + + // The user’s login name. + UserLogin string `json:"user_login"` + + // The user’s display name. + UserName string `json:"user_name"` +} + +// Gets all users allowed to moderate the broadcaster’s chat room. +// +// Requires a user access token that includes the moderation:read scope. +// If your app also adds and removes moderators, you can use the channel:manage:moderators scope instead. +func (m *Moderation) GetModerators(ctx context.Context, params *GetModeratorsParams) (*GetModeratorsResponse, error) { + v, _ := query.Values(params) + endpoint := m.baseUrl.ResolveReference(&url.URL{Path: "moderation/moderators", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) + if err != nil { + return nil, err + } + + res, err := m.client.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + var data GetModeratorsResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/api/moderation/get_shield_mode_status.go b/api/moderation/get_shield_mode_status.go new file mode 100644 index 0000000..faafd8e --- /dev/null +++ b/api/moderation/get_shield_mode_status.go @@ -0,0 +1,52 @@ +package moderation + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" +) + +type GetShieldModeStatusParams struct { + // The ID of the broadcaster whose Shield Mode activation status you want to get. + BroadcasterID string `url:"broadcaster_id"` + + // The ID of the broadcaster or a user that is one of the broadcaster’s moderators. This ID must match the user ID in the access token. + ModeratorID string `url:"moderator_id"` +} + +type GetShieldModeStatusResponse struct { + // A list that contains a single object with the broadcaster’s Shield Mode status. + Data []ShieldModeStatus `json:"data"` +} + +// Gets the broadcaster’s Shield Mode activation status. +// +// To receive notification when the broadcaster activates and deactivates Shield Mode, +// subscribe to the channel.shield_mode.begin and channel.shield_mode.end subscription types. +// +// Requires a user access token that includes the moderator:read:shield_mode or moderator:manage:shield_mode scope. +func (m *Moderation) GetShieldModeStatus(ctx context.Context, params *GetShieldModeStatusParams) (*GetShieldModeStatusResponse, error) { + v, _ := query.Values(params) + endpoint := m.baseUrl.ResolveReference(&url.URL{Path: "moderation/shield_mode", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) + if err != nil { + return nil, err + } + + res, err := m.client.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + var data GetShieldModeStatusResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/api/moderation/get_vips.go b/api/moderation/get_vips.go new file mode 100644 index 0000000..8b72ebe --- /dev/null +++ b/api/moderation/get_vips.go @@ -0,0 +1,79 @@ +package moderation + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" + "go.fifitido.net/twitch/api/types" +) + +type GetVIPsParams struct { + // Filters the list for specific VIPs. + // To specify more than one user, include the user_id parameter for each user to get. For example, &user_id=1234&user_id=5678. + // The maximum number of IDs that you may specify is 100. + // Ignores the ID of those users in the list that aren’t VIPs. + UserIDs []string `url:"user_id"` + + // The ID of the broadcaster whose list of VIPs you want to get. This ID must match the user ID in the access token. + BroadcasterID string `url:"broadcaster_id"` + + // The maximum number of items to return per page in the response. + // The minimum page size is 1 item per page and the maximum is 100 items per page. + // The default is 20. + First *int `url:"first"` + + // The cursor used to get the next page of results. + // The Pagination object in the response contains the cursor’s value. + // Read More: https://dev.twitch.tv/docs/api/guide#pagination + After *types.Cursor `url:"after"` +} + +type GetVIPsResponse struct { + // The list of VIPs. The list contains a single object that contains all the VIPs. + Data []GetVIPsResponseData `json:"data"` + + // Contains information about the pagination in the response. + // The object is empty if there are no more pages of results. + // Read More: https://dev.twitch.tv/docs/api/guide#pagination + Pagination types.Pagination `json:"pagination"` +} + +type GetVIPsResponseData struct { + // An ID that uniquely identifies the VIP user. + UserID string `json:"user_id"` + + // The user’s display name. + UserName string `json:"user_name"` + + // The user’s login name. + UserLogin string `json:"user_login"` +} + +// Gets a list of the broadcaster’s VIPs. +// +// Requires a user access token that includes the channel:read:vips scope. +// If your app also adds and removes VIP status, you can use the channel:manage:vips scope instead. +func (m *Moderation) GetVIPs(ctx context.Context, params GetVIPsParams) (*GetVIPsResponse, error) { + v, _ := query.Values(params) + endpoint := m.baseUrl.ResolveReference(&url.URL{Path: "moderation/vips", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) + if err != nil { + return nil, err + } + + res, err := m.client.Do(req) + if err != nil { + return nil, err + } + + var data GetVIPsResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/api/moderation/manage_held_automod_messages.go b/api/moderation/manage_held_automod_messages.go new file mode 100644 index 0000000..9b3e37a --- /dev/null +++ b/api/moderation/manage_held_automod_messages.go @@ -0,0 +1,49 @@ +package moderation + +import ( + "context" + "net/http" + "net/url" +) + +type AutoModAction string + +const ( + AutoModActionAllow AutoModAction = "ALLOW" + AutoModActionDeny AutoModAction = "DENY" +) + +type ManageHeldAutoModMessagesRequest struct { + // The moderator who is approving or denying the held message. This ID must match the user ID in the access token. + UserID string `url:"user_id"` + + // The ID of the message to allow or deny. + MsgID string `url:"msg_id"` + + // The action to take for the message. + Action AutoModAction `url:"action"` +} + +// Allow or deny the message that AutoMod flagged for review. +// For information about AutoMod, see How to Use AutoMod: https://help.twitch.tv/s/article/how-to-use-automod +// +// To get messages that AutoMod is holding for review, subscribe to the automod-queue.. topic using PubSub. +// PubSub sends a notification to your app when AutoMod holds a message for review. +// +// Requires a user access token that includes the moderator:manage:automod scope. +func (m *Moderation) ManageHeldAutoModMessages(ctx context.Context, body *ManageHeldAutoModMessagesRequest) error { + endpoint := m.baseUrl.ResolveReference(&url.URL{Path: "moderation/automod/message"}) + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), nil) + if err != nil { + return err + } + + res, err := m.client.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + return nil +} diff --git a/api/moderation/models.go b/api/moderation/models.go new file mode 100644 index 0000000..294fa7d --- /dev/null +++ b/api/moderation/models.go @@ -0,0 +1,85 @@ +package moderation + +import "time" + +type AutoModSettings struct { + // The ID of the broadcaster whose AutoMod settings you want to get. + BroadcasterID string `json:"broadcaster_id"` + + // The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. + // This ID must match the user ID in the user access token. + ModeratorID string `json:"moderator_id"` + + // The default AutoMod level for the broadcaster. This field is null if the broadcaster has set one or more of the individual settings. + OverallLevel int `json:"overall_level"` + + // The Automod level for discrimination against disability. + Disability int `json:"disability"` + + // The Automod level for hostility involving aggression. + Aggression int `json:"aggression"` + + // The Automod level for discrimination based on sexuality, sex, or gender. + SexualitySexOrGender int `json:"sexuality_sex_or_gender"` + + // The Automod level for discrimination against women. + Misogyny int `json:"misogyny"` + + // The Automod level for hostility involving name calling or insults. + Bullying int `json:"bullying"` + + // The Automod level for profanity. + Swearing int `json:"swearing"` + + // The Automod level for racial discrimination. + RaceEthnicityOrReligion int `json:"race_ethnicity_or_religion"` + + // The Automod level for sexual content. + SexBasedTerms int `json:"sex_based_terms"` +} + +type BlockedTerm struct { + // The broadcaster that owns the list of blocked terms. + BroadcasterID string `json:"broadcaster_id"` + + // The moderator that blocked the word or phrase from being used in the broadcaster’s chat room. + ModeratorID string `json:"moderator_id"` + + // An ID that identifies this blocked term. + ID string `json:"id"` + + // The blocked word or phrase. + Text string `json:"text"` + + // The UTC date and time (in RFC3339 format) that the term was blocked. + CreatedAt time.Time `json:"created_at"` + + // The UTC date and time (in RFC3339 format) that the term was updated. + // + // When the term is added, this timestamp is the same as created_at. + // The timestamp changes as AutoMod continues to deny the term. + UpdatedAt time.Time `json:"updated_at"` + + // The UTC date and time (in RFC3339 format) that the blocked term is set to expire. + // After the block expires, users may use the term in the broadcaster’s chat room. + // + // This field is null if the term was added manually or was permanently blocked by AutoMod. + ExpiresAt *time.Time `json:"expires_at"` +} + +type ShieldModeStatus struct { + // A Boolean value that determines whether Shield Mode is active. Is true if Shield Mode is active; otherwise, false. + IsActive bool `json:"is_active"` + + // An ID that identifies the moderator that last activated Shield Mode. + ModeratorID string `json:"moderator_id"` + + // The moderator’s login name. + ModeratorLogin string `json:"moderator_login"` + + // The moderator’s display name. + ModeratorName string `json:"moderator_name"` + + // The UTC timestamp (in RFC3339 format) of when Shield Mode was last activated. + LastActivatedAt time.Time `json:"last_activated_at"` +} diff --git a/api/moderation/moderation.go b/api/moderation/moderation.go new file mode 100644 index 0000000..09dd7cf --- /dev/null +++ b/api/moderation/moderation.go @@ -0,0 +1,18 @@ +package moderation + +import ( + "net/http" + "net/url" +) + +type Moderation struct { + client *http.Client + baseUrl *url.URL +} + +func New(client *http.Client, baseUrl *url.URL) *Moderation { + return &Moderation{ + client: client, + baseUrl: baseUrl, + } +} diff --git a/api/moderation/remove_blocked_term.go b/api/moderation/remove_blocked_term.go new file mode 100644 index 0000000..ef0240c --- /dev/null +++ b/api/moderation/remove_blocked_term.go @@ -0,0 +1,42 @@ +package moderation + +import ( + "context" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" +) + +type RemoveBlockedTermParams struct { + // The ID of the broadcaster that owns the list of blocked terms. + BroadcasterID string `url:"broadcaster_id"` + + // The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. + // This ID must match the user ID in the user access token. + ModeratorID string `url:"moderator_id"` + + // The ID of the blocked term to remove from the broadcaster’s list of blocked terms. + ID string `url:"id"` +} + +// Removes the word or phrase from the broadcaster’s list of blocked terms. +// +// Requires a user access token that includes the moderator:manage:blocked_terms scope. +func (m *Moderation) RemoveBlockedTerm(ctx context.Context, params *RemoveBlockedTermParams) error { + v, _ := query.Values(params) + endpoint := m.baseUrl.ResolveReference(&url.URL{Path: "moderation/blocks", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, endpoint.String(), nil) + if err != nil { + return err + } + + res, err := m.client.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + return nil +} diff --git a/api/moderation/remove_channel_moderator.go b/api/moderation/remove_channel_moderator.go new file mode 100644 index 0000000..36de55b --- /dev/null +++ b/api/moderation/remove_channel_moderator.go @@ -0,0 +1,40 @@ +package moderation + +import ( + "context" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" +) + +type RemoveChannelModeratorParams struct { + // The ID of the broadcaster that owns the chat room. This ID must match the user ID in the access token. + BroadcasterID string `url:"broadcaster_id"` + + // The ID of the user to remove as a moderator from the broadcaster’s chat room. + UserID string `url:"user_id"` +} + +// Removes a moderator from the broadcaster’s chat room. +// +// Rate Limits: The broadcaster may remove a maximum of 10 moderators within a 10-second window. +// +// Requires a user access token that includes the channel:manage:moderators scope. +func (m *Moderation) RemoveChannelModerator(ctx context.Context, params *RemoveChannelModeratorParams) error { + v, _ := query.Values(params) + endpoint := m.baseUrl.ResolveReference(&url.URL{Path: "moderation/moderators", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, endpoint.String(), nil) + if err != nil { + return err + } + + res, err := m.client.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + return nil +} diff --git a/api/moderation/remove_channel_vip.go b/api/moderation/remove_channel_vip.go new file mode 100644 index 0000000..9cbc3ed --- /dev/null +++ b/api/moderation/remove_channel_vip.go @@ -0,0 +1,43 @@ +package moderation + +import ( + "context" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" +) + +type RemoveChannelVIPParams struct { + // The ID of the user to remove VIP status from. + UserID string `url:"user_id"` + + // The ID of the broadcaster who owns the channel where the user has VIP status. + BroadcasterID string `url:"broadcaster_id"` +} + +// Removes the specified user as a VIP in the broadcaster’s channel. +// +// If the broadcaster is removing the user’s VIP status, the ID in the broadcaster_id query parameter must match the user ID in the access token; +// otherwise, if the user is removing their VIP status themselves, the ID in the user_id query parameter must match the user ID in the access token. +// +// Rate Limits: The broadcaster may remove a maximum of 10 VIPs within a 10-second window. +// +// Requires a user access token that includes the channel:manage:vips scope. +func (m *Moderation) RemoveChannelVIP(ctx context.Context, params *RemoveChannelVIPParams) error { + v, _ := query.Values(params) + endpoint := m.baseUrl.ResolveReference(&url.URL{Path: "channels/vips", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, endpoint.String(), nil) + if err != nil { + return err + } + + res, err := m.client.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + return nil +} diff --git a/api/moderation/unban_user.go b/api/moderation/unban_user.go new file mode 100644 index 0000000..a738ecd --- /dev/null +++ b/api/moderation/unban_user.go @@ -0,0 +1,44 @@ +package moderation + +import ( + "context" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" +) + +type UnbanUserParams struct { + // The ID of the broadcaster whose chat room the user is banned from chatting in. + BroadcasterID string `url:"broadcaster_id"` + + // The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. + // This ID must match the user ID in the user access token. + ModeratorID string `url:"moderator_id"` + + // The ID of the user to remove the ban or timeout from. + UserID string `url:"user_id"` +} + +// Removes the ban or timeout that was placed on the specified user. +// +// # To ban a user, see Ban user +// +// Requires a user access token that includes the moderator:manage:banned_users scope. +func (m *Moderation) UnbanUser(ctx context.Context, params *UnbanUserParams) error { + v, _ := query.Values(params) + endpoint := m.baseUrl.ResolveReference(&url.URL{Path: "moderation/bans", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, endpoint.String(), nil) + if err != nil { + return err + } + + res, err := m.client.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + return nil +} diff --git a/api/moderation/update_automod_settings.go b/api/moderation/update_automod_settings.go new file mode 100644 index 0000000..0a88888 --- /dev/null +++ b/api/moderation/update_automod_settings.go @@ -0,0 +1,110 @@ +package moderation + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" +) + +type UpdateAutoModSettingsParams struct { + // The ID of the broadcaster whose AutoMod settings you want to update. + BroadcasterID string `url:"broadcaster_id"` + + // The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. + // This ID must match the user ID in the user access token. + ModeratorID string `url:"moderator_id"` +} + +// Because PUT is an overwrite operation, you must include all the fields that you want set after the operation completes. +// Typically, you’ll send a GET request, update the fields you want to change, and pass that object in the PUT request. +// +// You may set either overall_level or the individual settings like aggression, but not both. +// +// Setting overall_level applies default values to the individual settings. +// However, setting overall_level to 4 does not necessarily mean that it applies 4 to all the individual settings. +// Instead, it applies a set of recommended defaults to the rest of the settings. +// For example, if you set overall_level to 2, Twitch provides some filtering on discrimination and sexual content, +// but more filtering on hostility (see the first example response). +// +// If overall_level is currently set and you update swearing to 3, overall_level will be set to null and all settings other than swearing will be set to 0. +// The same is true if individual settings are set and you update overall_level to 3 — all the individual settings are updated to reflect the default level. +// +// Note that if you set all the individual settings to values that match what overall_level would have set them to, +// Twitch changes AutoMod to use the default AutoMod level instead of using the individual settings. +// +// Valid values for all levels are from 0 (no filtering) through 4 (most aggressive filtering). +// These levels affect how aggressively AutoMod holds back messages for moderators to review before they appear in chat or are denied (not shown). +type UpdateAutoModSettingsRequest struct { + // The Automod level for hostility involving aggression. + Aggression int `json:"aggression"` + + // The Automod level for hostility involving name calling or insults. + Bullying int `json:"bullying"` + + // The Automod level for discrimination against disability. + Disability int `json:"disability"` + + // The Automod level for discrimination against women. + Misogyny int `json:"misogyny"` + + // The default AutoMod level for the broadcaster. + OverallLevel int `json:"overall_level"` + + // The Automod level for discrimination based on sexuality, sex, or gender. + RaceEthnicityOrReligion int `json:"race_ethnicity_or_religion"` + + // The Automod level for sexual content. + SexBasedTerms int `json:"sex_based_terms"` + + // The Automod level for discrimination against sexuality, sex, or gender. + SexualitySexOrGender int `json:"sexuality_sex_or_gender"` + + // The Automod level for profanity. + Swearing int `json:"swearing"` +} + +type UpdateAutoModSettingsResponse struct { + // The list of AutoMod settings. The list contains a single object that contains all the AutoMod settings. + Data []AutoModSettings `json:"data"` +} + +// Updates the broadcaster’s AutoMod settings. +// The settings are used to automatically block inappropriate or harassing messages from appearing in the broadcaster’s chat room. +// +// Requires a user access token that includes the moderator:manage:automod_settings scope. +func (m *Moderation) UpdateAutoModSettings(ctx context.Context, params *UpdateAutoModSettingsParams, body *UpdateAutoModSettingsRequest) (*UpdateAutoModSettingsResponse, error) { + v, _ := query.Values(params) + endpoint := m.baseUrl.ResolveReference(&url.URL{Path: "moderation/automod/settings", RawQuery: v.Encode()}) + + r, w := io.Pipe() + + go func() { + if err := json.NewEncoder(w).Encode(body); err != nil { + w.CloseWithError(err) + } else { + w.Close() + } + }() + + req, err := http.NewRequestWithContext(ctx, http.MethodPut, endpoint.String(), r) + if err != nil { + return nil, err + } + + res, err := m.client.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + var data UpdateAutoModSettingsResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/api/moderation/update_shield_mode_status.go b/api/moderation/update_shield_mode_status.go new file mode 100644 index 0000000..bb44704 --- /dev/null +++ b/api/moderation/update_shield_mode_status.go @@ -0,0 +1,69 @@ +package moderation + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" +) + +type UpdateShieldModeStatusParams struct { + // The ID of the broadcaster whose Shield Mode you want to activate or deactivate. + BroadcasterID string `url:"broadcaster_id"` + + // The ID of the broadcaster or a user that is one of the broadcaster’s moderators. This ID must match the user ID in the access token. + ModeratorID string `url:"moderator_id"` +} + +type UpdateShieldModeStatusRequest struct { + // A Boolean value that determines whether to activate Shield Mode. Set to true to activate Shield Mode; otherwise, false to deactivate Shield Mode. + IsActive bool `json:"is_active"` +} + +type UpdateShieldModeStatusResponse struct { + // A list that contains a single object with the broadcaster’s updated Shield Mode status. + Data []ShieldModeStatus `json:"data"` +} + +// Activates or deactivates the broadcaster’s Shield Mode. +// +// Twitch’s Shield Mode feature is like a panic button that broadcasters can push to protect themselves from chat abuse coming from one or more accounts. +// When activated, Shield Mode applies the overrides that the broadcaster configured in the Twitch UX. +// If the broadcaster hasn’t configured Shield Mode, it applies default overrides. +// +// Requires a user access token that includes the moderator:manage:shield_mode scope. +func (m *Moderation) UpdateShieldModeStatus(ctx context.Context, params *UpdateShieldModeStatusParams) (*UpdateShieldModeStatusResponse, error) { + v, _ := query.Values(params) + endpoint := m.baseUrl.ResolveReference(&url.URL{Path: "moderation/shield_mode", RawQuery: v.Encode()}) + + r, w := io.Pipe() + + go func() { + if err := json.NewEncoder(w).Encode(UpdateShieldModeStatusRequest{IsActive: true}); err != nil { + w.CloseWithError(err) + } else { + w.Close() + } + }() + + req, err := http.NewRequestWithContext(ctx, http.MethodPatch, endpoint.String(), r) + if err != nil { + return nil, err + } + + res, err := m.client.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + var data UpdateShieldModeStatusResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +}