diff --git a/api/api.go b/api/api.go index ba7515d..6ec27d6 100644 --- a/api/api.go +++ b/api/api.go @@ -10,6 +10,7 @@ import ( "go.fifitido.net/twitch/api/channelpoints" "go.fifitido.net/twitch/api/channels" "go.fifitido.net/twitch/api/charity" + "go.fifitido.net/twitch/api/chat" "go.fifitido.net/twitch/api/eventsub" ) @@ -25,6 +26,7 @@ type API struct { Channels *channels.Channels ChannelPoints *channelpoints.ChannelPoints Charity *charity.Charity + Chat *chat.Chat EventSub *eventsub.EventSub } @@ -42,6 +44,7 @@ func New() *API { Channels: channels.New(client, baseUrl), ChannelPoints: channelpoints.New(client, baseUrl), Charity: charity.New(client, baseUrl), + Chat: chat.New(client, baseUrl), EventSub: eventsub.New(client, baseUrl), } } diff --git a/api/chat/chat.go b/api/chat/chat.go new file mode 100644 index 0000000..b3c7d1f --- /dev/null +++ b/api/chat/chat.go @@ -0,0 +1,18 @@ +package chat + +import ( + "net/http" + "net/url" +) + +type Chat struct { + client *http.Client + baseUrl *url.URL +} + +func New(client *http.Client, baseUrl *url.URL) *Chat { + return &Chat{ + client: client, + baseUrl: baseUrl, + } +} diff --git a/api/chat/get_channel_chat_badges.go b/api/chat/get_channel_chat_badges.go new file mode 100644 index 0000000..4e088bd --- /dev/null +++ b/api/chat/get_channel_chat_badges.go @@ -0,0 +1,41 @@ +package chat + +import ( + "context" + "encoding/json" + "net/http" + "net/url" +) + +type GetChannelChatBadgesResponse struct { + // The list of chat badges. The list is sorted in ascending order by set_id, and within a set, the list is sorted in ascending order by id. + Data []Badge `json:"data"` +} + +// Gets the broadcaster’s list of custom chat badges. The list is empty if the broadcaster hasn’t created custom chat badges. +// For information about custom badges, +// see subscriber badges: https://help.twitch.tv/s/article/subscriber-badge-guide +// and Bits badges: https://help.twitch.tv/s/article/custom-bit-badges-guide. +// +// Requires an app access token or user access token. +func (c *Chat) GetChannelChatBadges(ctx context.Context, broadcasterID string) (*GetChannelChatBadgesResponse, error) { + endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "chat/badges", RawQuery: "broadcaster_id=" + broadcasterID}) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, 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 GetChannelChatBadgesResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/api/chat/get_channel_emotes.go b/api/chat/get_channel_emotes.go new file mode 100644 index 0000000..54b0e0d --- /dev/null +++ b/api/chat/get_channel_emotes.go @@ -0,0 +1,110 @@ +package chat + +import ( + "context" + "encoding/json" + "net/http" + "net/url" +) + +type GetChannelEmotesResponse struct { + // The list of emotes that the specified broadcaster created. + // If the broadcaster hasn't created custom emotes, the list is empty. + Data []ChannelEmote `json:"data"` + + // A templated URL. Use the values from the id, format, scale, and theme_mode fields to replace the like-named placeholder strings + // in the templated URL to create a CDN (content delivery network) URL that you use to fetch the emote. + // For information about what the template looks like and how to use it to fetch emotes. + // See Emote CDN URL format: https://dev.twitch.tv/docs/irc/emotes#cdn-template + // You should use this template instead of using the URLs in the images object. + Template string `json:"template"` +} + +type ChannelEmote struct { + // An ID that identifies this emote. + ID string `json:"id"` + + // The name of the emote. This is the name that viewers type in the chat window to get the emote to appear. + Name string `json:"name"` + + // The image URLs for the emote. These image URLs always provide a static, non-animated emote image with a light background. + // + // NOTE: You should use the templated URL in the template field to fetch the image instead of using these URLs. + Images EmoteImages `json:"images"` + + // The subscriber tier at which the emote is unlocked. + // This field contains the tier information only if emote_type is set to subscriptions, otherwise, it's an empty string. + Tier string `json:"tier"` + + // The type of emote. The possible values are: + // + // bitstier — A custom Bits tier emote. + // + // follower — A custom follower emote. + // + // subscriptions — A custom subscriber emote. + EmoteType string `json:"emote_type"` + + // An ID that identifies the emote set that the emote belongs to. + EmoteSetID string `json:"emote_set_id"` + + // The formats that the emote is available in. + // For example, if the emote is available only as a static PNG, the array contains only static. + // But if the emote is available as a static PNG and an animated GIF, the array contains static and animated. + // The possible formats are: + // + // animated — An animated GIF is available for this emote. + // + // static — A static PNG file is available for this emote. + Formats []EmoteFormat `json:"format"` + + // The sizes that the emote is available in. + // For example, if the emote is available in small and medium sizes, the array contains 1.0 and 2.0. + // Possible sizes are: + // + // 1.0 — A small version (28px x 28px) is available. + // + // 2.0 — A medium version (56px x 56px) is available. + // + // 3.0 — A large version (112px x 112px) is available. + Scales []EmoteScale `json:"scale"` + + // The background themes that the emote is available in. Possible themes are: + // + // dark, light + ThemeMode []EmoteThemeMode `json:"theme_mode"` +} + +// Gets the broadcaster’s list of custom emotes. +// Broadcasters create these custom emotes for users who subscribe to or follow the channel or cheer Bits in the channel’s chat window. +// Learn More: https://dev.twitch.tv/docs/irc/emotes +// +// For information about the custom emotes +// see subscriber emotes: https://help.twitch.tv/s/article/subscriber-emote-guide, +// Bits tier emotes: https://help.twitch.tv/s/article/custom-bit-badges-guide?language=bg#slots, +// and follower emotes: https://blog.twitch.tv/en/2021/06/04/kicking-off-10-years-with-our-biggest-emote-update-ever/ +// +// NOTE: With the exception of custom follower emotes, users may use custom emotes in any Twitch chat. +// +// Requires an app access token or user access token. +func (c *Chat) GetChannelEmotes(ctx context.Context, broadcasterID string) (*GetChannelEmotesResponse, error) { + endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "chat/emotes", RawQuery: "broadcaster_id=" + broadcasterID}) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) + if err != nil { + return nil, err + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var response GetChannelEmotesResponse + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return nil, err + } + + return &response, nil +} diff --git a/api/chat/get_chat_settings.go b/api/chat/get_chat_settings.go new file mode 100644 index 0000000..efa1362 --- /dev/null +++ b/api/chat/get_chat_settings.go @@ -0,0 +1,57 @@ +package chat + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" +) + +type GetChatSettingsParams struct { + // The ID of the broadcaster whose chat settings you want to get. + BroadcasterID string `url:"broadcaster_id"` + + // The ID of the broadcaster or one of the broadcaster’s moderators. + // + // This field is required only if you want to include the non_moderator_chat_delay and non_moderator_chat_delay_duration settings in the response. + // + // If you specify this field, this ID must match the user ID in the user access token. + ModeratorID *string `url:"moderator_id,omitempty"` +} + +type GetChatSettingsResponse struct { + // The list of chat settings. The list contains a single object with all the settings. + Data []Settings `json:"data"` +} + +// Gets the broadcaster’s chat settings. +// +// For an overview of chat settings, +// see Chat Commands for Broadcasters and Moderators: https://help.twitch.tv/s/article/chat-commands#AllMods +// and Moderator Preferences: https://help.twitch.tv/s/article/setting-up-moderation-for-your-twitch-channel#modpreferences +// +// Requires an app access token or user access token. +func (c *Chat) GetChatSettings(ctx context.Context, params *GetChatSettingsParams) (*GetChatSettingsResponse, error) { + v, _ := query.Values(params) + endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "chat/settings", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, 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 GetChatSettingsResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/api/chat/get_chatters.go b/api/chat/get_chatters.go new file mode 100644 index 0000000..c21ceec --- /dev/null +++ b/api/chat/get_chatters.go @@ -0,0 +1,84 @@ +package chat + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" + "go.fifitido.net/twitch/api/types" +) + +type GetChattersParams struct { + // The ID of the broadcaster whose list of chatters you want to get. + BroadcasterID string `url:"broadcaster_id"` + + // The ID of the broadcaster or one of the broadcaster’s moderators. + // 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 1,000. + // The default is 100. + First *int `url:"first,omitempty"` + + // 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,omitempty"` +} + +type GetChattersResponse struct { + // The list of users that are connected to the broadcaster’s chat room. + // The list is empty if no users are connected to the chat room. + Data []Chatter `json:"data"` + + // Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. + // Read more: https://dev.twitch.tv/docs/api/guide#pagination + Pagination types.Pagination `json:"pagination"` + + // The total number of users that are connected to the broadcaster’s chat room. + // As you page through the list, the number of users may change as users join and leave the chat room. + Total int `json:"total"` +} + +type Chatter struct { + // The ID of a user that’s connected to the broadcaster’s chat room. + 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 the list of users that are connected to the broadcaster’s chat session. +// +// NOTE: There is a delay between when users join and leave a chat and when the list is updated accordingly. +// +// To determine whether a user is a moderator or VIP, use the Get Moderators and Get VIPs endpoints. You can check the roles of up to 100 users. +// +// Requires a user access token that includes the moderator:read:chatters scope. +func (c *Chat) GetChatters(ctx context.Context, params *GetChattersParams) (*GetChattersResponse, error) { + v, _ := query.Values(params) + endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "chat/chatters", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) + if err != nil { + return nil, err + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var response GetChattersResponse + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return nil, err + } + + return &response, nil +} diff --git a/api/chat/get_emote_sets.go b/api/chat/get_emote_sets.go new file mode 100644 index 0000000..e3fee81 --- /dev/null +++ b/api/chat/get_emote_sets.go @@ -0,0 +1,116 @@ +package chat + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" +) + +type GetEmoteSetsParams struct { + // An ID that identifies the emote set to get. Include this parameter for each emote set you want to get. + // For example, emote_set_id=1234&emote_set_id=5678. You may specify a maximum of 25 IDs. + // The response contains only the IDs that were found and ignores duplicate IDs. + // + // To get emote set IDs, use the Get Channel Emotes API. + EmoteSetIDs []string `url:"emote_set_id,omitempty"` +} + +type GetEmoteSetsResponse struct { + // The list of emotes found in the specified emote sets. The list is empty if none of the IDs were found. + // The list is in the same order as the set IDs specified in the request. Each set contains one or more emoticons. + Data []EmoteSetEmote `json:"data"` + + // A templated URL. Use the values from the id, format, scale, and theme_mode fields to replace the like-named placeholder strings + // in the templated URL to create a CDN (content delivery network) URL that you use to fetch the emote. + // For information about what the template looks like and how to use it to fetch emotes. + // See Emote CDN URL format: https://dev.twitch.tv/docs/irc/emotes#cdn-template + // You should use this template instead of using the URLs in the images object. + Template string `json:"template"` +} + +type EmoteSetEmote struct { + // An ID that identifies this emote. + ID string `json:"id"` + + // The name of the emote. This is the name that viewers type in the chat window to get the emote to appear. + Name string `json:"name"` + + // The image URLs for the emote. These image URLs always provide a static, non-animated emote image with a light background. + // + // NOTE: You should use the templated URL in the template field to fetch the image instead of using these URLs. + Images EmoteImages `json:"images"` + + // The type of emote. The possible values are: + // + // bitstier — A custom Bits tier emote. + // + // follower — A custom follower emote. + // + // subscriptions — A custom subscriber emote. + EmoteType string `json:"emote_type"` + + // An ID that identifies the emote set that the emote belongs to. + EmoteSetID string `json:"emote_set_id"` + + // The ID of the broadcaster who owns the emote. + OwnerID string `json:"owner_id"` + + // The formats that the emote is available in. + // For example, if the emote is available only as a static PNG, the array contains only static. + // But if the emote is available as a static PNG and an animated GIF, the array contains static and animated. + // The possible formats are: + // + // animated — An animated GIF is available for this emote. + // + // static — A static PNG file is available for this emote. + Formats []EmoteFormat `json:"format"` + + // The sizes that the emote is available in. + // For example, if the emote is available in small and medium sizes, the array contains 1.0 and 2.0. + // Possible sizes are: + // + // 1.0 — A small version (28px x 28px) is available. + // + // 2.0 — A medium version (56px x 56px) is available. + // + // 3.0 — A large version (112px x 112px) is available. + Scales []EmoteScale `json:"scale"` + + // The background themes that the emote is available in. Possible themes are: + // + // dark, light + ThemeMode []EmoteThemeMode `json:"theme_mode"` +} + +// Gets emotes for one or more specified emote sets. +// +// An emote set groups emotes that have a similar context. For example, Twitch places all the subscriber emotes that a broadcaster uploads for their channel in the same emote set. +// +// Learn More: https://dev.twitch.tv/docs/irc/emotes +// +// Requires an app access token or user access token. +func (c *Chat) GetEmoteSets(ctx context.Context, params *GetEmoteSetsParams) (*GetEmoteSetsResponse, error) { + v, _ := query.Values(params) + endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "chat/emotes/set", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, 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 GetEmoteSetsResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/api/chat/get_global_chat_badges.go b/api/chat/get_global_chat_badges.go new file mode 100644 index 0000000..d59eadc --- /dev/null +++ b/api/chat/get_global_chat_badges.go @@ -0,0 +1,39 @@ +package chat + +import ( + "context" + "encoding/json" + "net/http" + "net/url" +) + +type GetGlobalChatBadgesResponse struct { + // The list of chat badges. The list is sorted in ascending order by set_id, and within a set, the list is sorted in ascending order by id. + Data []Badge `json:"data"` +} + +// Gets Twitch’s list of chat badges, which users may use in any channel’s chat room. +// For information about chat badges, see Twitch Chat Badges Guide: https://help.twitch.tv/s/article/twitch-chat-badges-guide +// +// Requires an app access token or user access token. +func (c *Chat) GetGlobalChatBadges(ctx context.Context) (*GetGlobalChatBadgesResponse, error) { + endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "chat/badges/global"}) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, 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 GetGlobalChatBadgesResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/api/chat/get_global_emotes.go b/api/chat/get_global_emotes.go new file mode 100644 index 0000000..ab1c713 --- /dev/null +++ b/api/chat/get_global_emotes.go @@ -0,0 +1,86 @@ +package chat + +import ( + "context" + "encoding/json" + "net/http" + "net/url" +) + +type GetGlobalEmotesResponse struct { + // The list of global emotes. + Data []GlobalEmote `json:"data"` + + // A templated URL. Use the values from the id, format, scale, and theme_mode fields to replace the like-named placeholder strings + // in the templated URL to create a CDN (content delivery network) URL that you use to fetch the emote. + // For information about what the template looks like and how to use it to fetch emotes. + // See Emote CDN URL format: https://dev.twitch.tv/docs/irc/emotes#cdn-template + // You should use this template instead of using the URLs in the images object. + Template string `json:"template"` +} + +type GlobalEmote struct { + // An ID that identifies this emote. + ID string `json:"id"` + + // The name of the emote. This is the name that viewers type in the chat window to get the emote to appear. + Name string `json:"name"` + + // The image URLs for the emote. These image URLs always provide a static, non-animated emote image with a light background. + // + // NOTE: You should use the templated URL in the template field to fetch the image instead of using these URLs. + Images EmoteImages `json:"images"` + + // The formats that the emote is available in. + // For example, if the emote is available only as a static PNG, the array contains only static. + // But if the emote is available as a static PNG and an animated GIF, the array contains static and animated. + // The possible formats are: + // + // animated — An animated GIF is available for this emote. + // + // static — A static PNG file is available for this emote. + Formats []EmoteFormat `json:"format"` + + // The sizes that the emote is available in. + // For example, if the emote is available in small and medium sizes, the array contains 1.0 and 2.0. + // Possible sizes are: + // + // 1.0 — A small version (28px x 28px) is available. + // + // 2.0 — A medium version (56px x 56px) is available. + // + // 3.0 — A large version (112px x 112px) is available. + Scales []EmoteScale `json:"scale"` + + // The background themes that the emote is available in. Possible themes are: + // + // dark, light + ThemeMode []EmoteThemeMode `json:"theme_mode"` +} + +// Gets the list of global emotes. Global emotes are Twitch-created emotes that users can use in any Twitch chat. +// +// Learn More: https://dev.twitch.tv/docs/irc/emotes +// +// Requires an app access token or user access token. +func (c *Chat) GetGlobalEmotes(ctx context.Context) (*GetGlobalEmotesResponse, error) { + endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "chat/emotes/global"}) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, 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 GetGlobalEmotesResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/api/chat/get_user_chat_color.go b/api/chat/get_user_chat_color.go new file mode 100644 index 0000000..2148b79 --- /dev/null +++ b/api/chat/get_user_chat_color.go @@ -0,0 +1,65 @@ +package chat + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" +) + +type GetUserChatColorParams struct { + // The ID of the user whose username color you want to get. + // 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. + // + // The API ignores duplicate IDs and IDs that weren’t found. + UserIDs []string `url:"user_id"` +} + +type GetUserChatColorResponse struct { + // The list of users and the color code they use for their name. + Data []UserChatColor `json:"data"` +} + +type UserChatColor struct { + // An ID that uniquely identifies the user. + 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"` + + // The Hex color code that the user uses in chat for their name. + // If the user hasn’t specified a color in their settings, the string is empty. + Color string `json:"color"` +} + +// Gets the color used for the user’s name in chat. +// +// Requires an app access token or user access token. +func (c *Chat) GetUserChatColor(ctx context.Context, params *GetUserChatColorParams) (*GetUserChatColorResponse, error) { + v, _ := query.Values(params) + endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "chat/color", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, 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 GetUserChatColorResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/api/chat/models.go b/api/chat/models.go new file mode 100644 index 0000000..a4eaa56 --- /dev/null +++ b/api/chat/models.go @@ -0,0 +1,134 @@ +package chat + +type EmoteImages struct { + // A URL to the small version (28px x 28px) of the emote. + URL1x string `json:"url_1x"` + + // A URL to the medium version (56px x 56px) of the emote. + URL2x string `json:"url_2x"` + + // A URL to the large version (112px x 112px) of the emote. + URL4x string `json:"url_4x"` +} + +type EmoteFormat string + +const ( + EmoteFormatStatic EmoteFormat = "static" + EmoteFormatAnimated EmoteFormat = "animated" +) + +type EmoteScale string + +const ( + // 1.0 — A small version (28px x 28px) is available. + EmoteScale1 EmoteScale = "1.0" + + // 2.0 — A medium version (56px x 56px) is available. + EmoteScale2 EmoteScale = "2x" + + // 3.0 — A large version (112px x 112px) is available. + EmoteScale4 EmoteScale = "4x" +) + +type EmoteThemeMode string + +const ( + EmoteThemeModeDark EmoteThemeMode = "dark" + EmoteThemeModeLight EmoteThemeMode = "light" +) + +type Badge struct { + // An ID that identifies this set of chat badges. For example, Bits or Subscriber. + SetID string `json:"set_id"` + + // The list of chat badges in this set. + Versions []BadgeVersion `json:"versions"` +} + +type BadgeVersion struct { + // An ID that identifies this version of the badge. The ID can be any value. + // For example, for Bits, the ID is the Bits tier level, but for World of Warcraft, it could be Alliance or Horde. + ID string `json:"id"` + + // A URL to the small version (18px x 18px) of the badge. + ImageURL1x string `json:"image_url_1x"` + + // A URL to the medium version (36px x 36px) of the badge. + ImageURL2x string `json:"image_url_2x"` + + // A URL to the large version (72px x 72px) of the badge. + ImageURL4x string `json:"image_url_4x"` + + // The title of the badge. + Title string `json:"title"` + + // The description of the badge. + Description string `json:"description"` + + // The action to take when clicking on the badge. Set to null if no action is specified. + ClickAction *string `json:"click_action"` + + // The URL to navigate to when clicking on the badge. Set to null if no URL is specified. + ClickURL *string `json:"click_url"` +} + +type Settings struct { + // The ID of the broadcaster specified in the request. + BroadcasterID string `json:"broadcaster_id"` + + // A Boolean value that determines whether chat messages must contain only emotes. Is true if chat messages may contain only emotes; otherwise, false. + EmoteMode bool `json:"emote_mode"` + + // A Boolean value that determines whether the broadcaster restricts the chat room to followers only. + // + // Is true if the broadcaster restricts the chat room to followers only; otherwise, false. + // + // See the follower_mode_duration field for how long users must follow the broadcaster before being able to participate in the chat room. + FollowerMode bool `json:"follower_mode"` + + // The length of time, in minutes, that users must follow the broadcaster before being able to participate in the chat room. + // Is null if follower_mode is false. + FollowerModeDuration *int `json:"follower_mode_duration"` + + // The moderator’s ID. + // The response includes this field only if the request specifies a user access token that includes the moderator:read:chat_settings scope. + ModeratorID *string `json:"moderator_id"` + + // A Boolean value that determines whether the broadcaster adds a short delay before chat messages appear in the chat room. + // This gives chat moderators and bots a chance to remove them before viewers can see the message. + // See the non_moderator_chat_delay_duration field for the length of the delay. + // Is true if the broadcaster applies a delay; otherwise, false. + // + // The response includes this field only if the request specifies a user access token that includes the moderator:read:chat_settings + // scope and the user in the moderator_id query parameter is one of the broadcaster’s moderators. + NonModeratorChatDelay *bool `json:"non_moderator_chat_delay"` + + // The amount of time, in seconds, that messages are delayed before appearing in chat. Is null if non_moderator_chat_delay is false. + // + // The response includes this field only if the request specifies a user access token that includes the moderator:read:chat_settings + // scope and the user in the moderator_id query parameter is one of the broadcaster’s moderators. + NonModeratorChatDelayDuration *int `json:"non_moderator_chat_delay_duration"` + + // A Boolean value that determines whether the broadcaster limits how often users in the chat room are allowed to send messages. + // + // Is true if the broadcaster applies a delay; otherwise, false. + // + // See the slow_mode_wait_time field for the delay. + SlowMode bool `json:"slow_mode"` + + // The amount of time, in seconds, that users must wait between sending messages. + // + // Is null if slow_mode is false. + SlowModeWaitTime *int `json:"slow_mode_wait_time"` + + // A Boolean value that determines whether only users that subscribe to the broadcaster’s channel may talk in the chat room. + // + // Is true if the broadcaster restricts the chat room to subscribers only; otherwise, false. + SubscriberMode bool `json:"subscriber_mode"` + + // A Boolean value that determines whether the broadcaster requires users to post only unique messages in the chat room. + // + // Is true if the broadcaster requires unique messages only; otherwise, false. + UniqueChatMode bool `json:"unique_chat_mode"` +} diff --git a/api/chat/send_chat_announcement.go b/api/chat/send_chat_announcement.go new file mode 100644 index 0000000..c9e2e3d --- /dev/null +++ b/api/chat/send_chat_announcement.go @@ -0,0 +1,64 @@ +package chat + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" + "go.fifitido.net/twitch/api/types" +) + +type SendChatAnnouncementParams struct { + // The ID of the broadcaster that owns the chat room to send the announcement to. + BroadcasterID string `url:"broadcaster_id"` + + // The ID of a user who has permission to moderate the broadcaster’s chat room, or the broadcaster’s ID if they’re sending the announcement. + // This ID must match the user ID in the user access token. + ModeratorID string `url:"moderator_id"` +} + +type SendChatAnnouncementRequest struct { + // The announcement to make in the broadcaster’s chat room. + // Announcements are limited to a maximum of 500 characters; announcements longer than 500 characters are truncated. + Message string `json:"message"` + + // The color used to highlight the announcement. Possible case-sensitive values are: + // + // If color is set to primary or is not set, the channel’s accent color is used to highlight the announcement + // (see Profile Accent Color under profile settings, Channel and Videos, and Brand). + Color *types.AccentColor `json:"color"` +} + +// Sends an announcement to the broadcaster’s chat room. +// +// Requires a user access token that includes the moderator:manage:announcements scope. +func (c *Chat) SendChatAnnouncement(ctx context.Context, params *SendChatAnnouncementParams, body *SendChatAnnouncementRequest) error { + v, _ := query.Values(params) + endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "chat/announcements", 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 err + } + + res, err := c.client.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + return nil +} diff --git a/api/chat/send_chat_message.go b/api/chat/send_chat_message.go new file mode 100644 index 0000000..5b27539 --- /dev/null +++ b/api/chat/send_chat_message.go @@ -0,0 +1,85 @@ +package chat + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/url" +) + +type SendChatMessageRequest struct { + // The ID of the broadcaster whose chat room the message will be sent to. + BroadcasterID string `json:"broadcaster_id"` + + // The ID of the user sending the message. This ID must match the user ID in the user access token. + SenderID string `json:"sender_id"` + + // The message to send. The message is limited to a maximum of 500 characters. Chat messages can also include emoticons. + // To include emoticons, use the name of the emote. The names are case sensitive. Don’t include colons around the name (e.g., :bleedPurple:). + // If Twitch recognizes the name, Twitch converts the name to the emote before writing the chat message to the chat room + Message string `json:"message"` + + // The ID of the chat message being replied to. + ReplyParentMessageID *string `json:"reply_parent_message_id,omitempty"` +} + +type SendChatMessageResponse struct { + Data []SendChatMessageData `json:"data"` +} + +type SendChatMessageData struct { + // The message id for the message that was sent. + MessageID string `json:"message_id"` + + // If the message passed all checks and was sent + IsSent bool `json:"is_sent"` + + // The reason the message was dropped, if any. + DropReason []DropReason `json:"drop_reason,omitempty"` +} + +type DropReason struct { + // Code for why the message was dropped. + Code string `json:"code"` + + // Message for why the message was dropped. + Message string `json:"message"` +} + +// Sends a message to the broadcaster’s chat room. +// +// Requires an app access token or user access token that includes the user:write:chat scope. +// If app access token used, then additionally requires user:bot scope from chatting user, +// and either channel:bot scope from broadcaster or moderator status. +func (c *Chat) SendChatMessage(ctx context.Context, body *SendChatMessageRequest) (*SendChatMessageResponse, error) { + endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "chat/messages"}) + + 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 := c.client.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + var data SendChatMessageResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/api/chat/send_shoutout.go b/api/chat/send_shoutout.go new file mode 100644 index 0000000..869c745 --- /dev/null +++ b/api/chat/send_shoutout.go @@ -0,0 +1,55 @@ +package chat + +import ( + "context" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" +) + +type SendShoutoutParams struct { + // The ID of the broadcaster that’s sending the Shoutout. + FromBroadcasterID string `url:"from_broadcaster_id"` + + // The ID of the broadcaster that’s receiving the Shoutout. + ToBroadcasterID string `url:"to_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"` +} + +// Sends a Shoutout to the specified broadcaster. +// Typically, you send Shoutouts when you or one of your moderators notice another broadcaster in your chat, +// the other broadcaster is coming up in conversation, or after they raid your broadcast. +// +// Twitch’s Shoutout feature is a great way for you to show support for other broadcasters and help them grow. +// Viewers who do not follow the other broadcaster will see a pop-up Follow button in your chat that they can click to follow the other broadcaster. +// Learn More: https://help.twitch.tv/s/article/shoutouts +// +// Rate Limits The broadcaster may send a Shoutout once every 2 minutes. +// They may send the same broadcaster a Shoutout once every 60 minutes. +// +// To receive notifications when a Shoutout is sent or received, subscribe to the channel.shoutout.create and channel.shoutout.receive subscription types. +// The channel.shoutout.create event includes cooldown periods that indicate when the broadcaster may send another +// Shoutout without exceeding the endpoint’s rate limit. +// +// Requires a user access token that includes the moderator:manage:shoutouts scope. +func (c *Chat) SendShoutout(ctx context.Context, params *SendShoutoutParams) error { + v, _ := query.Values(params) + endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "chat/shoutouts", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), nil) + if err != nil { + return err + } + + res, err := c.client.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + return nil +} diff --git a/api/chat/update_chat_settings.go b/api/chat/update_chat_settings.go new file mode 100644 index 0000000..25519d5 --- /dev/null +++ b/api/chat/update_chat_settings.go @@ -0,0 +1,125 @@ +package chat + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" +) + +type UpdateChatSettingsParams struct { + // The ID of the broadcaster whose chat settings you want to update. + BroadcasterID string `url:"broadcaster_id"` + + // The ID of a user that has permission to moderate the broadcaster’s chat room, or the broadcaster’s ID if they’re making the update. + // This ID must match the user ID in the user access token. + ModeratorID string `url:"moderator_id"` +} + +type UpdateChatSettingsRequest struct { + // A Boolean value that determines whether chat messages must contain only emotes. + // + // Set to true if only emotes are allowed; otherwise, false. The default is false. + EmoteMode *bool `json:"emote_mode"` + + // A Boolean value that determines whether the broadcaster restricts the chat room to followers only. + // + // Set to true if the broadcaster restricts the chat room to followers only; otherwise, false. The default is true. + // + // To specify how long users must follow the broadcaster before being able to participate in the chat room, see the follower_mode_duration field. + FollowerMode *bool `json:"follower_mode"` + + // The length of time, in minutes, that users must follow the broadcaster before being able to participate in the chat room. + // Set only if follower_mode is true. + // Possible values are: 0 (no restriction) through 129600 (3 months). + // The default is 0. + FollowerModeDuration *int `json:"follower_mode_duration"` + + // A Boolean value that determines whether the broadcaster adds a short delay before chat messages appear in the chat room. + // This gives chat moderators and bots a chance to remove them before viewers can see the message. + // + // Set to true if the broadcaster applies a delay; otherwise, false. + // The default is false. + // + // To specify the length of the delay, see the non_moderator_chat_delay_duration field. + NonModeratorChatDelay *bool `json:"non_moderator_chat_delay"` + + // The amount of time, in seconds, that messages are delayed before appearing in chat. Set only if non_moderator_chat_delay is true. + // Possible values are: + // + // 2 — 2 second delay (recommended) + // + // 4 — 4 second delay + // + // 6 — 6 second delay + NonModeratorChatDelayDuration *int `json:"non_moderator_chat_delay_duration"` + + // A Boolean value that determines whether the broadcaster limits how often users in the chat room are allowed to send messages. + // Set to true if the broadcaster applies a wait period between messages; otherwise, false. + // The default is false. + // + // To specify the delay, see the slow_mode_wait_time field. + SlowMode bool `json:"slow_mode"` + + // The amount of time, in seconds, that users must wait between sending messages. + // Set only if slow_mode is true. + // + // Possible values are: 3 (3 second delay) through 120 (2 minute delay). The default is 30 seconds. + SlowModeWaitTime *int `json:"slow_mode_wait_time"` + + // A Boolean value that determines whether only users that subscribe to the broadcaster’s channel may talk in the chat room. + // + // Set to true if the broadcaster restricts the chat room to subscribers only; otherwise, false. + // The default is false. + SubscriberMode *bool `json:"subscriber_mode"` + + // A Boolean value that determines whether the broadcaster requires users to post only unique messages in the chat room. + // + // Set to true if the broadcaster allows only unique messages; otherwise, false. + // The default is false. + UniqueChatMode *bool `json:"unique_chat_mode"` +} + +type UpdateChatSettingsResponse struct { + // The list of chat settings. The list contains a single object with all the settings. + Data []Settings `json:"data"` +} + +// Updates the broadcaster’s chat settings. +// +// Requires a user access token that includes the moderator:manage:chat_settings scope. +func (c *Chat) UpdateChatSettings(ctx context.Context, params *UpdateChatSettingsParams, body *UpdateChatSettingsRequest) (*UpdateChatSettingsResponse, error) { + v, _ := query.Values(body) + endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "chat/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.MethodPatch, endpoint.String(), r) + if err != nil { + return nil, err + } + + res, err := c.client.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + var data UpdateChatSettingsResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/api/chat/update_user_chat_color.go b/api/chat/update_user_chat_color.go new file mode 100644 index 0000000..cbcbc98 --- /dev/null +++ b/api/chat/update_user_chat_color.go @@ -0,0 +1,43 @@ +package chat + +import ( + "context" + "net/http" + "net/url" + + "github.com/google/go-querystring/query" +) + +type UpdateUserChatColorParams struct { + // The ID of the user whose chat color you want to update. This ID must match the user ID in the access token. + UserID string `url:"user_id"` + + // The color to use for the user's name in chat. All users may specify one of the following named color values: + // + // blue, blue_violet, cadet_blue, chocolate, coral, dodger_blue, firebrick + // golden_rod, green, hot_pink, orange_red, red, sea_green, spring_green, yellow_green + // + // Turbo and Prime users may specify a named color or a Hex color code like #9146FF. If you use a Hex color code, remember to URL encode it. + Color string `url:"color"` +} + +// Updates the color used for the user’s name in chat. +// +// Requires a user access token that includes the user:manage:chat_color scope. +func (c *Chat) UpdateUserChatColor(ctx context.Context, params *UpdateUserChatColorParams) error { + v, _ := query.Values(params) + endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "chat/color", RawQuery: v.Encode()}) + + req, err := http.NewRequestWithContext(ctx, http.MethodPut, endpoint.String(), nil) + if err != nil { + return err + } + + res, err := c.client.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + return nil +} diff --git a/api/types/types.go b/api/types/types.go index 902285e..7e51450 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -81,3 +81,13 @@ type CurrencyAmount struct { // The ISO-4217 three-letter currency code that identifies the type of currency in value. Currency string `json:"currency"` } + +type AccentColor string + +const ( + AccentColorPrimary AccentColor = "primary" + AccentColorBlue AccentColor = "blue" + AccentColorGreen AccentColor = "green" + AccentColorOrange AccentColor = "orange" + AccentColorPurple AccentColor = "purple" +) diff --git a/ptr_types.go b/ptr_types.go index de3f95c..c49310f 100644 --- a/ptr_types.go +++ b/ptr_types.go @@ -150,3 +150,14 @@ func ToSortOrder(s *types.SortOrder) types.SortOrder { } return *s } + +func AccentColor(s types.AccentColor) *types.AccentColor { + return &s +} + +func ToAccentColor(s *types.AccentColor) types.AccentColor { + if s == nil { + return "" + } + return *s +}