Add conduit endpoints to API

This commit is contained in:
Evan Fiordeliso 2024-02-28 13:50:07 -05:00
parent 990b1da80c
commit e1b6b5ce88
10 changed files with 423 additions and 0 deletions

View File

@ -11,6 +11,7 @@ import (
"go.fifitido.net/twitch/api/channels" "go.fifitido.net/twitch/api/channels"
"go.fifitido.net/twitch/api/charity" "go.fifitido.net/twitch/api/charity"
"go.fifitido.net/twitch/api/chat" "go.fifitido.net/twitch/api/chat"
"go.fifitido.net/twitch/api/conduit"
"go.fifitido.net/twitch/api/eventsub" "go.fifitido.net/twitch/api/eventsub"
) )
@ -27,6 +28,7 @@ type API struct {
ChannelPoints *channelpoints.ChannelPoints ChannelPoints *channelpoints.ChannelPoints
Charity *charity.Charity Charity *charity.Charity
Chat *chat.Chat Chat *chat.Chat
Conduit *conduit.Conduit
EventSub *eventsub.EventSub EventSub *eventsub.EventSub
} }
@ -45,6 +47,7 @@ func New() *API {
ChannelPoints: channelpoints.New(client, baseUrl), ChannelPoints: channelpoints.New(client, baseUrl),
Charity: charity.New(client, baseUrl), Charity: charity.New(client, baseUrl),
Chat: chat.New(client, baseUrl), Chat: chat.New(client, baseUrl),
Conduit: conduit.New(client, baseUrl),
EventSub: eventsub.New(client, baseUrl), EventSub: eventsub.New(client, baseUrl),
} }
} }

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

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

View File

@ -0,0 +1,38 @@
package conduit
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
)
type CreateConduitsResponse struct {
Data []ConduitData `json:"data"`
}
// Creates a new conduit.
//
// Requires an app access token.
func (c *Conduit) CreateConduits(ctx context.Context, shareCount int) (*CreateConduitsResponse, error) {
endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "eventsub/conduits", RawQuery: "share_count=" + fmt.Sprint(shareCount)})
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 CreateConduitsResponse
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
return nil, err
}
return &data, nil
}

View File

@ -0,0 +1,28 @@
package conduit
import (
"context"
"net/http"
"net/url"
)
// Deletes a specified conduit.
// Note that it may take some time for Eventsub subscriptions on a deleted conduit to show as disabled when calling Get Eventsub Subscriptions.
//
// Requires an app access token.
func (c *Conduit) DeleteConduit(ctx context.Context, id string) error {
endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "eventsub/conduits", RawQuery: "id=" + id})
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, 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
}

View File

@ -0,0 +1,56 @@
package conduit
import (
"context"
"encoding/json"
"net/http"
"net/url"
"github.com/google/go-querystring/query"
"go.fifitido.net/twitch/api/types"
)
type GetConduitShardsParams struct {
// Conduit ID.
ConduitID string `url:"conduit_id"`
// Status to filter by.
Status *Status `url:"status,omitempty"`
// The cursor used to get the next page of results. The pagination object in the response contains the cursors value.
Cursor *types.Cursor `url:"cursor,omitempty"`
}
type GetConduitShardsResponse struct {
// List of information about a conduit's shards.
Data []Shard `json:"data"`
// The cursor used to get the next page of results. Use the cursor to set the requests after query parameter.
Pagination types.Pagination `json:"pagination"`
}
// Gets a lists of all shards for a conduit.
//
// Requires an app access token.
func (c *Conduit) GetConduitShards(ctx context.Context, params *GetConduitShardsParams) (*GetConduitShardsResponse, error) {
v, _ := query.Values(params)
endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "eventsub/conduits/shards", 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 GetConduitShardsResponse
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
return nil, err
}
return &data, nil
}

View File

@ -0,0 +1,38 @@
package conduit
import (
"context"
"encoding/json"
"net/http"
"net/url"
)
type GetConduitsResponse struct {
// List of information about the clients conduits.
Data []ConduitData `json:"data"`
}
// Gets the conduits for a client ID.
//
// Requires an app access token.
func (c *Conduit) GetConduits(ctx context.Context) (*GetConduitsResponse, error) {
endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "eventsub/conduits"})
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 GetConduitsResponse
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
return nil, err
}
return &data, nil
}

76
api/conduit/models.go Normal file
View File

@ -0,0 +1,76 @@
package conduit
import "time"
type ConduitData struct {
// Conduit ID.
ID string `json:"id"`
// Number of shards associated with this conduit.
ShardCount int `json:"shard_count"`
}
type Shard struct {
// Shard ID.
ID string `json:"id"`
// The shard status. The subscriber receives events only for enabled shards
Status Status `json:"status"`
// The transport details used to send the notifications.
Transport *Transport `json:"transport"`
}
type Status string
const (
// enabled — The shard is enabled.
StatusEnabled Status = "enabled"
// webhook_callback_verification_pending — The shard is pending verification of the specified callback URL.
StatusWebhookCallbackVerificationPending Status = "webhook_callback_verification_pending"
// webhook_callback_verification_failed — The specified callback URL failed verification.
StatusWebhookCallbackVerificationFailed Status = "webhook_callback_verification_failed"
// notification_failures_exceeded — The notification delivery failure rate was too high.
NotificationFailuresExceeded Status = "notification_failures_exceeded"
// websocket_disconnected — The client closed the connection.
WebsocketDisconnected Status = "websocket_disconnected"
// websocket_failed_ping_pong — The client failed to respond to a ping message.
WebsocketFailedPingPong Status = "websocket_failed_ping_pong"
// websocket_received_inbound_traffic — The client sent a non-pong message.
// Clients may only send pong messages (and only in response to a ping message).
WebsocketReceivedInboundTraffic Status = "websocket_received_inbound_traffic"
// websocket_connection_unused — The client failed to subscribe to events within the required time.
WebsocketConnectionUnused Status = "websocket_connection_unused"
// websocket_internal_error — The Twitch WebSocket server experienced an unexpected error.
WebsocketInternalError Status = "websocket_internal_error"
// websocket_network_timeout — The Twitch WebSocket server timed out writing the message to the client.
WebsocketNetworkTimeout Status = "websocket_network_timeout"
// websocket_network_error — The Twitch WebSocket server experienced a network error writing the message to the client.
WebsocketnetworkError Status = "websocket_network_error"
)
type Transport struct {
// The transport method. Possible values are:
//
// webhook, websocket
Method string `json:"method"`
// The callback URL where the notifications are sent. Included only if method is set to webhook.
Callback *string `json:"callback,omitempty"`
// The UTC date and time that the WebSocket connection was established. Included only if method is set to websocket.
ConnectedAt *time.Time `json:"connected_at,omitempty"`
// The UTC date and time that the WebSocket connection was lost. Included only if method is set to websocket.
DisconnectedAt *time.Time `json:"disconnected_at,omitempty"`
}

View File

@ -0,0 +1,96 @@
package conduit
import (
"context"
"encoding/json"
"io"
"net/http"
"net/url"
)
type UpdateConduitShardsRequest struct {
// Conduit ID.
ConduitID string `json:"conduit_id"`
// List of Shards to update.
Shards []ShardUpdate `json:"shards"`
}
type ShardUpdate struct {
// Shard ID.
ID string `json:"id"`
// The transport details that you want Twitch to use when sending you notifications.
Transport *Transport `json:"transport"`
}
type UpdateConduitShardsResponse struct {
// List of successful shard updates.
Data []ConduitData `json:"data"`
// List of unsuccessful updates.
Errors []UpdateConduitShardsError `json:"errors"`
}
type UpdateConduitShardsError struct {
// Shard ID.
ID string `json:"id"`
// The error that occurred while updating the shard.
// Possible errors:
//
// The length of the string in the secret field is not valid.
//
// The URL in the transport's callback field is not valid. The URL must use the HTTPS protocol and the 443 port number.
//
// The value specified in the method field is not valid.
//
// The callback field is required if you specify the webhook transport method.
//
// The session_id field is required if you specify the WebSocket transport method.
//
// The websocket session is not connected.
//
// The shard id is outside of the conduits range.
Message string `json:"message"`
// Error codes used to represent a specific error condition while attempting to update shards.
Code string `json:"code"`
}
// Updates shard(s) for a conduit.
//
// NOTE: Shard IDs are indexed starting at 0, so a conduit with a shard_count of 5 will have shards with IDs 0 through 4.
//
// Requires an app access token.
func (c *Conduit) UpdateConduitShards(ctx context.Context, body *UpdateConduitShardsRequest) (*UpdateConduitShardsResponse, error) {
endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "eventsub/conduits/shards"})
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 UpdateConduitShardsResponse
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
return nil, err
}
return &data, nil
}

View File

@ -0,0 +1,58 @@
package conduit
import (
"context"
"encoding/json"
"io"
"net/http"
"net/url"
)
type UpdateConduitsRequest struct {
// Conduit ID.
ID string `json:"id"`
// The new number of shards for this conduit.
ShardCount int `json:"shard_count"`
}
type UpdateConduitsResponse struct {
// List of information about the clients conduits.
Data []ConduitData `json:"data"`
}
// Updates a conduits shard count. To delete shards, update the count to a lower number, and the shards above the count will be deleted.
// For example, if the existing shard count is 100, by resetting shard count to 50, shards 50-99 are disabled.
//
// Requires an app access token.
func (c *Conduit) UpdateConduits(ctx context.Context, body *UpdateConduitsRequest) (*UpdateConduitsResponse, error) {
endpoint := c.baseUrl.ResolveReference(&url.URL{Path: "eventsub/conduits"})
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 UpdateConduitsResponse
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
return nil, err
}
return &data, nil
}

View File

@ -5,6 +5,7 @@ import (
"go.fifitido.net/twitch/api/bits" "go.fifitido.net/twitch/api/bits"
"go.fifitido.net/twitch/api/channelpoints" "go.fifitido.net/twitch/api/channelpoints"
"go.fifitido.net/twitch/api/conduit"
"go.fifitido.net/twitch/api/types" "go.fifitido.net/twitch/api/types"
) )
@ -161,3 +162,14 @@ func ToAccentColor(s *types.AccentColor) types.AccentColor {
} }
return *s return *s
} }
func ConduitStatus(s conduit.Status) *conduit.Status {
return &s
}
func ToConduitStatus(s *conduit.Status) conduit.Status {
if s == nil {
return ""
}
return *s
}