Add Users endpoints to API

This commit is contained in:
Evan Fiordeliso 2024-03-03 18:34:26 -05:00
parent 55b5f772fe
commit 695e998b1f
11 changed files with 572 additions and 0 deletions

View File

@ -29,6 +29,7 @@ import (
"go.fifitido.net/twitch/api/streams" "go.fifitido.net/twitch/api/streams"
"go.fifitido.net/twitch/api/subscriptions" "go.fifitido.net/twitch/api/subscriptions"
"go.fifitido.net/twitch/api/teams" "go.fifitido.net/twitch/api/teams"
"go.fifitido.net/twitch/api/users"
) )
const HelixBaseUrl = "https://api.twitch.tv/helix" const HelixBaseUrl = "https://api.twitch.tv/helix"
@ -62,6 +63,7 @@ type API struct {
Streams *streams.Streams Streams *streams.Streams
Subscriptions *subscriptions.Subscriptions Subscriptions *subscriptions.Subscriptions
Teams *teams.Teams Teams *teams.Teams
Users *users.Users
} }
func New(client *http.Client, baseUrl *url.URL) *API { func New(client *http.Client, baseUrl *url.URL) *API {
@ -94,6 +96,7 @@ func New(client *http.Client, baseUrl *url.URL) *API {
Streams: streams.New(client, baseUrl), Streams: streams.New(client, baseUrl),
Subscriptions: subscriptions.New(client, baseUrl), Subscriptions: subscriptions.New(client, baseUrl),
Teams: teams.New(client, baseUrl), Teams: teams.New(client, baseUrl),
Users: users.New(client, baseUrl),
} }
} }

49
api/users/block_user.go Normal file
View File

@ -0,0 +1,49 @@
package users
import (
"context"
"net/http"
"net/url"
"github.com/google/go-querystring/query"
)
type BlockUserParams struct {
// The ID of the user to block. The API ignores the request if the broadcaster has already blocked the user.
TargetUserID string `url:"target_user_id"`
// The location where the harassment took place that is causing the brodcaster to block the user. Possible values are:
//
// chat, whisper
SourceContext *string `url:"source_context,omitempty"`
// The reason that the broadcaster is blocking the user. Possible values are:
//
// harassment, spam, other
Reason *string `url:"reason,omitempty"`
}
// Blocks the specified user from interacting with or having contact with the broadcaster.
// The user ID in the OAuth token identifies the broadcaster who is blocking the user.
//
// To learn more about blocking users,
// see Block Other Users on Twitch: https://help.twitch.tv/s/article/how-to-manage-harassment-in-chat?language=en_US#BlockWhispersandMessagesfromStrangers
//
// Requires a user access token that includes the user:manage:blocked_users scope.
func (u *Users) BlockUser(ctx context.Context, params *BlockUserParams) error {
v, _ := query.Values(params)
endpoint := u.baseUrl.ResolveReference(&url.URL{Path: "users/blocks", RawQuery: v.Encode()})
req, err := http.NewRequestWithContext(ctx, http.MethodPut, endpoint.String(), nil)
if err != nil {
return err
}
res, err := u.client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
return nil
}

View File

@ -0,0 +1,52 @@
package users
import (
"context"
"encoding/json"
"net/http"
"net/url"
"github.com/google/go-querystring/query"
)
type GetUserActiveExtensionsParams struct {
// The ID of the broadcaster whose active extensions you want to get.
//
// This parameter is required if you specify an app access token and is optional if you specify a user access token.
// If you specify a user access token and dont specify this parameter, the API uses the user ID from the access token.
UserID string `url:"user_id,omitempty"`
}
type GetUserActiveExtensionsResponse struct {
// The active extensions that the broadcaster has installed.
Data []ActiveExtension `json:"data"`
}
// Gets the active extensions that the broadcaster has installed for each configuration.
//
// NOTE: To include extensions that you have under development,
// you must specify a user access token that includes the user:read:broadcast or user:edit:broadcast scope.
//
// Requires an app access token or user access token.
func (u *Users) GetUserActiveExtensions(ctx context.Context, params *GetUserActiveExtensionsParams) (*GetUserActiveExtensionsResponse, error) {
v, _ := query.Values(params)
endpoint := u.baseUrl.ResolveReference(&url.URL{Path: "users/extensions", RawQuery: v.Encode()})
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
if err != nil {
return nil, err
}
res, err := u.client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
var data GetUserActiveExtensionsResponse
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
return nil, err
}
return &data, nil
}

View File

@ -0,0 +1,67 @@
package users
import (
"context"
"encoding/json"
"net/http"
"net/url"
"github.com/google/go-querystring/query"
"go.fifitido.net/twitch/api/types"
)
type GetUserBlockListParams struct {
// The ID of the broadcaster whose list of blocked users you want to get.
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.
// The default is 20.
First *int `url:"first,omitempty"`
// The cursor used to get the next page of results.
// The Pagination object in the response contains the cursors value.
// Read More: https://dev.twitch.tv/docs/api/guide#pagination
After *types.Cursor `url:"after,omitempty"`
}
type GetUserBlockListResponse struct {
// The list of blocked users. The list is in descending order by when the user was blocked.
Data []struct {
// An ID that identifies the blocked user.
UserID string `json:"user_id"`
// The blocked users login name.
UserLogin string `json:"user_login"`
// The blocked users display name.
DisplayName string `json:"display_name"`
} `json:"data"`
}
// Gets the list of users that the broadcaster has blocked.
// Read More: https://help.twitch.tv/s/article/how-to-manage-harassment-in-chat?language=en_US#BlockWhispersandMessagesfromStrangers
//
// Requires a user access token that includes the user:read:blocked_users scope.
func (u *Users) GetUserBlockList(ctx context.Context, params *GetUserBlockListParams) (*GetUserBlockListResponse, error) {
v, _ := query.Values(params)
endpoint := u.baseUrl.ResolveReference(&url.URL{Path: "users/blocks", RawQuery: v.Encode()})
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
if err != nil {
return nil, err
}
res, err := u.client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
var data GetUserBlockListResponse
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
return nil, err
}
return &data, nil
}

View File

@ -0,0 +1,57 @@
package users
import (
"context"
"encoding/json"
"net/http"
"net/url"
)
type GetUserExtensionsResponse struct {
// The list of extensions that the user has installed.
Data []struct {
// An ID that identifies the extension.
ID string `json:"id"`
// The extension's version.
Version string `json:"version"`
// The extension's name.
Name string `json:"name"`
// A Boolean value that determines whether the extension is configured and can be activated. Is true if the extension is configured and can be activated.
CanActivate bool `json:"can_activate"`
// The extension types that you can activate for this extension. Possible values are:
//
// component, mobile, overlay, panel
Type []string `json:"type"`
} `json:"data"`
}
// Gets a list of all extensions (both active and inactive) that the broadcaster has installed.
// The user ID in the access token identifies the broadcaster.
//
// Requires a user access token that includes the user:read:broadcast or user:edit:broadcast scope.
// To include inactive extensions, you must include the user:edit:broadcast scope.
func (u *Users) GetUserExtensions(ctx context.Context) (*GetUserExtensionsResponse, error) {
endpoint := u.baseUrl.ResolveReference(&url.URL{Path: "users/extensions/list"})
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
if err != nil {
return nil, err
}
res, err := u.client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
var data GetUserExtensionsResponse
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
return nil, err
}
return &data, nil
}

60
api/users/get_users.go Normal file
View File

@ -0,0 +1,60 @@
package users
import (
"context"
"encoding/json"
"net/http"
"net/url"
"github.com/google/go-querystring/query"
)
type GetUsersParams struct {
// The ID of the user to get.
// To specify more than one user, include the id parameter for each user to get. For example, id=1234&id=5678.
// The maximum number of IDs you may specify is 100.
IDs []string `url:"id,omitempty"`
// The login name of the user to get.
// To specify more than one user, include the login parameter for each user to get. For example, login=foo&login=bar.
// The maximum number of login names you may specify is 100.
Logins []string `url:"login,omitempty"`
}
type GetUsersResponse struct {
// The list of users.
Data []User `json:"data"`
}
// Gets information about one or more users.
//
// You may look up users using their user ID, login name, or both but the sum total of the number of users you may look up is 100.
// For example, you may specify 50 IDs and 50 names or 100 IDs or names, but you cannot specify 100 IDs and 100 names.
//
// If you dont specify IDs or login names, the request returns information about the user in the access token if you specify a user access token.
//
// To include the users verified email address in the response, you must use a user access token that includes the user:read:email scope.
//
// Requires an app access token or user access token.
func (u *Users) GetUsers(ctx context.Context, params *GetUsersParams) (*GetUsersResponse, error) {
v, _ := query.Values(params)
endpoint := u.baseUrl.ResolveReference(&url.URL{Path: "users", RawQuery: v.Encode()})
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
if err != nil {
return nil, err
}
res, err := u.client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
var data GetUsersResponse
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
return nil, err
}
return &data, nil
}

122
api/users/models.go Normal file
View File

@ -0,0 +1,122 @@
package users
import "time"
type User struct {
// An ID that identifies the user.
ID string `json:"id"`
// The user's login name.
Login string `json:"login"`
// The user's display name.
DisplayName string `json:"display_name"`
// The type of user. Possible values are:
//
// admin — Twitch administrator
//
// global_mod
//
// staff — Twitch staff
//
// "" — Normal user
Type Type `json:"type"`
// The type of broadcaster. Possible values are:
//
// affiliate — An affiliate broadcaster
//
// partner — A partner broadcaster
//
// "" — A normal broadcaster
BroadcasterType BroadcasterType `json:"broadcaster_type"`
// The user's description of their channel.
Description string `json:"description"`
// A URL to the user's profile image.
ProfileImageUrl string `json:"profile_image_url"`
// A URL to the user's offline image.
OfflineImageUrl string `json:"offline_image_url"`
// The user's verified email address. The object includes this field only if the user access token includes the user:read:email scope.
//
// If the request contains more than one user, only the user associated with the access token that provided consent will include an email address —
// the email address for all other users will be empty.
Email *string `json:"email"`
// The UTC date and time that the user's account was created. The timestamp is in RFC3339 format.
CreatedAt time.Time `json:"created_at"`
}
type Type string
const (
// Twitch administrator
TypeAdmin Type = "admin"
// Twitch global moderator
TypeGlobalMod Type = "global_mod"
// Twitch staff
TypeStaff Type = "staff"
// Normal user
TypeNormal Type = ""
)
type BroadcasterType string
const (
// An affiliate broadcaster
BroadcasterTypeAffiliate BroadcasterType = "affiliate"
// A partner broadcaster
BroadcasterTypePartner BroadcasterType = "partner"
// A normal broadcaster
BroadcasterTypeNormal BroadcasterType = ""
)
type ActiveExtension struct {
// A dictionary that contains the data for a panel extension.
// The dictionarys key is a sequential number beginning with 1.
// The following fields contain the panels data for each key.
Panel map[string]ExtensionData `json:"panel"`
// A dictionary that contains the data for a video-overlay extension.
// The dictionarys key is a sequential number beginning with 1.
// The following fields contain the overlays data for each key.
Overlay map[string]ExtensionData `json:"overlay"`
// A dictionary that contains the data for a video-component extension.
// The dictionarys key is a sequential number beginning with 1.
// The following fields contain the components data for each key.
Component map[string]ComponentExtensionData `json:"component"`
}
type ExtensionData struct {
// A Boolean value that determines the extensions activation state. If false, the user has not configured this panel extension.
Active bool `json:"active"`
// An ID that identifies the extension.
ID string `json:"id"`
// The extensions version.
Version string `json:"version"`
// The extensions name.
Name string `json:"name"`
}
type ComponentExtensionData struct {
ExtensionData `json:",inline"`
// The x-coordinate where the extension is placed.
X int `json:"x"`
// The y-coordinate where the extension is placed.
Y int `json:"y"`
}

28
api/users/unblock_user.go Normal file
View File

@ -0,0 +1,28 @@
package users
import (
"context"
"net/http"
"net/url"
)
// Removes the user from the broadcasters list of blocked users.
// The user ID in the OAuth token identifies the broadcaster whos removing the block.
//
// Requires a user access token that includes the user:manage:blocked_users scope.
func (u *Users) UnblockUser(ctx context.Context, targetUserID string) error {
endpoint := u.baseUrl.ResolveReference(&url.URL{Path: "users/blocks", RawQuery: url.Values{"target_user_id": {targetUserID}}.Encode()})
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, endpoint.String(), nil)
if err != nil {
return err
}
res, err := u.client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
return nil
}

50
api/users/update_user.go Normal file
View File

@ -0,0 +1,50 @@
package users
import (
"context"
"encoding/json"
"net/http"
"net/url"
"github.com/google/go-querystring/query"
)
type UpdateUserParams struct {
// The string to update the channels description to. The description is limited to a maximum of 300 characters.
//
// To remove the description, specify this parameter but dont set its value (for example, ?description=).
Description *string `url:"description,omitempty"`
}
type UpdateUserResponse struct {
// A list contains the single user that you updated.
Data []User `json:"data"`
}
// Updates the specified users information. The user ID in the OAuth token identifies the user whose information you want to update.
//
// To include the users verified email address in the response, the user access token must also include the user:read:email scope.
//
// Requires a user access token that includes the user:edit scope.
func (u *Users) UpdateUser(ctx context.Context, params *UpdateUserParams) (*UpdateUserResponse, error) {
v, _ := query.Values(params)
endpoint := u.baseUrl.ResolveReference(&url.URL{Path: "users", RawQuery: v.Encode()})
req, err := http.NewRequestWithContext(ctx, http.MethodPatch, endpoint.String(), nil)
if err != nil {
return nil, err
}
res, err := u.client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
var data UpdateUserResponse
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
return nil, err
}
return &data, nil
}

View File

@ -0,0 +1,66 @@
package users
import (
"context"
"encoding/json"
"io"
"net/http"
"net/url"
)
type UpdateUserExtensionsRequest struct {
// The extensions to update. The data field is a dictionary of extension types.
// The dictionarys possible keys are: panel, overlay, or component.
// The keys value is a dictionary of extensions.
//
// For the extensions dictionary, the key is a sequential number beginning with 1.
// For panel and overlay extensions, the keys value is an object that contains the following fields:
// active (true/false), id (the extensions ID), and version (the extensions version).
//
// For component extensions, the keys value includes the above fields plus the x and y fields,
// which identify the coordinate where the extension is placed.
Data map[string]map[string]interface{} `json:"data"`
}
type UpdateUserExtensionsResponse struct {
// The extensions that the broadcaster updated.
Data []ActiveExtension `json:"data"`
}
// Updates an installed extensions information. You can update the extensions activation state, ID, and version number.
// The user ID in the access token identifies the broadcaster whose extensions youre updating.
//
// NOTE: If you try to activate an extension under multiple extension types, the last write wins (and there is no guarantee of write order).
//
// Requires a user access token that includes the user:edit:broadcast scope.
func (u *Users) UpdateUserExtensions(ctx context.Context, body *UpdateUserExtensionsRequest) (*UpdateUserExtensionsResponse, error) {
endpoint := u.baseUrl.ResolveReference(&url.URL{Path: "users/extensions"})
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 := u.client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
var data UpdateUserExtensionsResponse
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
return nil, err
}
return &data, nil
}

18
api/users/users.go Normal file
View File

@ -0,0 +1,18 @@
package users
import (
"net/http"
"net/url"
)
type Users struct {
client *http.Client
baseUrl *url.URL
}
func New(client *http.Client, baseUrl *url.URL) *Users {
return &Users{
client: client,
baseUrl: baseUrl,
}
}